none
std::locale::globalの呼び出しはスレッドセーフなのでしょうか? RRS feed

  • 質問

  • VC2010 SP1でマルチスレッドのアプリ開発しています。

    以前作成したアプリで複数のスレッドから「std::locale::global」を呼び出していたのですが

    稀にクラッシュすることが判り、調査をしていた所std::localeのデストラクタの近辺でクラッシュしていることがわかりました。

    MSDNのドキュメントにはマルチスレッドについては記載されていませんでした。

    このあたりの情報をお持ちの方はいないでしょうか?

    下に再現コードを張ります。

    暫らく実行続けているとクラッシュすることがあります。

    #include "stdafx.h"
    #include <locale>
    #include <process.h>
    #include <windows.h>
    
    using namespace std;
    
    void localetest_thread_func(void *){
    	static volatile LONG threadCount = 0;
    	static char const*const localenames[] ={
    		"C",
    		"japanese"
    	};
    
    	for(;;){
    		unsigned int n = ::InterlockedIncrement(&threadCount);
    		const char* name = localenames[n&1];
    
    		locale::global(locale(name)); //<-ここでクラッシュ
    
    		//printf_s("thread = %d name=%s\n",n,name);
    	}
    }
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	/*create some threades */
    	for(int i=0;i<10;++i){
    		::_beginthread(
    			&localetest_thread_func,
    			0,
    			NULL
    		);
    	}
    	::Sleep(INFINITE);
    
    	return 0;
    }


    • 編集済み skrs 2012年3月8日 9:23
    2012年3月8日 9:23

回答

  • 役に立ってなさそうですが、個人的な見解を追加です。

    昨日は書かなかったのですが、こちらの環境でもデバッグビルドではヒープの破損が出ていました。

    (リリースビルドではアクセスバイオレーションだったりイリーガルインストラクション・・・。)

    リリースビルドはヒープ操作の検証をコード側でしないので、保護されないメモリ操作がOSのシステム例外になっているのだと思います。

    再現コードでの状況は「ヒープ破損が原因で停止している。」ということでしょう。

    個人的な見解では

    「std::locale」がプロセス共有のヒープ使用をしているためスレッド毎にアロケートされるために使用できない領域が肥大化してる?

    という状況じゃないかなと推測してますがヒープの状況などは見てません。

    「std::locale」クラスの方も内部でロック付の参照カウントを使ってるようなのでスレッドセーフなのだと思っていましたが、

    実際にクラッシュしてしまうのでライブラリのバグかなと思って投稿しました。

    「std::locale」側もロック付の参照カウントを使ってるのならスレッドセーフを意識して作ってるのかも知れませんね。

    ライブラリ側 のバグと考えられるのならと思って Microsoft Connect サイトを検索すると似たようなバグ報告が見つかりました。

    フィードバックの回避策などを参照してみると良いかも知れません。

    http://connect.microsoft.com/VisualStudio/feedback/details/650567/unhomed-std-locale-facet-and-std-locale-locimp-destructors-cause-crashes

    http://connect.microsoft.com/VisualStudio/feedback/details/492128/std-locale-constructor-modifies-global-locale-via-setlocale

    • 回答としてマーク skrs 2012年3月9日 17:47
    2012年3月9日 4:10
  • Win32APIであれば、CreateMutexで名前付きオブジェクトを作成し、これをWaitForSingleObjects等で待たせることになると思います。名前がそのシステムでユニークになるような名前にする必要がありますが、長い文字列で共通にしてしまえば(偶然に他アプリと一致することは稀なので)正しく待機できると思います。

    ただ・・・大丈夫だとは思いますがbeginthread関連で実装したことがないので、beginthread内でCreateMutexやWaitFor等が動作するか分かりません。

    MutexのHandleそのものを別のスレッドで必要になるシチュエーションが思い浮かばないのですが、もしも必要な場合はちょっと悩ましいですね。

    • 回答としてマーク skrs 2012年3月9日 17:47
    2012年3月9日 10:58
  • 本筋ではないですが。

    Win32APIであれば、CreateMutexで名前付きオブジェクトを作成し、これをWaitForSingleObjects等で待たせることになると思います。名前がそのシステムでユニークになるような名前にする必要がありますが、長い文字列で共通にしてしまえば(偶然に他アプリと一致することは稀なので)正しく待機できると思います。

    同じプロセス内でなら、無名の Mutex なり、CriticalSection なりで十分だと思います。
    起動時にハンドルを作って、それが空くのを待てばよいだけでしょう。

    # 名前付きで実現するのはできなくもないですが、手段としてはちょっと行き過ぎな印象です。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。

    • 回答としてマーク skrs 2012年3月9日 17:47
    2012年3月9日 15:32
    モデレータ
  • Connect のフィードバックを眺めてると 「VS11 で対応する。」と答えてるので原因が同じなら直ってるかも?

    と思い立ち VS11 Developer Preview で再現コードをためしてみると再現しませんでした。

     VS11 で直りそうなのでバグなのでしょう。

    そうなると問題は回避策となりますが Connect の回避策を見る限りメモリ処理に不具合があるように思えます。

     同期処理に話が流れてるようですが、それで回避できればよいですけど・・・。

    • 回答としてマーク skrs 2012年3月9日 17:55
    2012年3月9日 17:15

すべての返信

  • こんな記述を見つけました。

    Thread Safety in the Standard C++ Library

    基本は「保護」のようです。

    2012年3月8日 13:52
  • あまり役に立ちそうにないですが、再現コードを試してみたので一応書込みします。

    「std::locale」クラスがヒープを壊してしまうようでスレッドセーフでない感触ですね。

    検索や MSDN のドキュメントに記載は見当たりませんが、回避することを考えるとスレッドセーフではないと思う方が良さそうです。

    「std::locale::global」静的関数の方はパッと見はスレッドセーフに見えたので「std::locale」クラスを使い捨てにしてヒープを酷使してるのが問題のようにも思えますが・・・・。

    2012年3月8日 14:07
  • 回答ありがとうございます。

    リンクのサイトを確認させてもらいました。

    コード上では、”single object”を複数スレッドで使ってるわけではないし(内部的には共有してしまっているみたいですが)、

    「std::locele」を「std::shared_ptr」で括ってやってもこのコードでは無意味ですよね。

    一応「std::locale::global」の呼び出し(「std::locale」のコンストラクト、デストラクト)をMutexやCriticalSectionで囲う方法で修正しようと思ってるんですが、

    実際に起きているコードは再現コードのように同じスレッドではないのでどうやってMutexを渡すのか考えどころです。

    2012年3月9日 0:37
  • 回答ありがとうございます。

    昨日は書かなかったのですが、こちらの環境でもデバッグビルドではヒープの破損が出ていました。

    (リリースビルドではアクセスバイオレーションだったりイリーガルインストラクション・・・。)

    「std::locale」クラスの方も内部でロック付の参照カウントを使ってるようなのでスレッドセーフなのだと思っていましたが、

    実際にクラッシュしてしまうのでライブラリのバグかなと思って投稿しました。

    2012年3月9日 0:53
  • 役に立ってなさそうですが、個人的な見解を追加です。

    昨日は書かなかったのですが、こちらの環境でもデバッグビルドではヒープの破損が出ていました。

    (リリースビルドではアクセスバイオレーションだったりイリーガルインストラクション・・・。)

    リリースビルドはヒープ操作の検証をコード側でしないので、保護されないメモリ操作がOSのシステム例外になっているのだと思います。

    再現コードでの状況は「ヒープ破損が原因で停止している。」ということでしょう。

    個人的な見解では

    「std::locale」がプロセス共有のヒープ使用をしているためスレッド毎にアロケートされるために使用できない領域が肥大化してる?

    という状況じゃないかなと推測してますがヒープの状況などは見てません。

    「std::locale」クラスの方も内部でロック付の参照カウントを使ってるようなのでスレッドセーフなのだと思っていましたが、

    実際にクラッシュしてしまうのでライブラリのバグかなと思って投稿しました。

    「std::locale」側もロック付の参照カウントを使ってるのならスレッドセーフを意識して作ってるのかも知れませんね。

    ライブラリ側 のバグと考えられるのならと思って Microsoft Connect サイトを検索すると似たようなバグ報告が見つかりました。

    フィードバックの回避策などを参照してみると良いかも知れません。

    http://connect.microsoft.com/VisualStudio/feedback/details/650567/unhomed-std-locale-facet-and-std-locale-locimp-destructors-cause-crashes

    http://connect.microsoft.com/VisualStudio/feedback/details/492128/std-locale-constructor-modifies-global-locale-via-setlocale

    • 回答としてマーク skrs 2012年3月9日 17:47
    2012年3月9日 4:10
  • Win32APIであれば、CreateMutexで名前付きオブジェクトを作成し、これをWaitForSingleObjects等で待たせることになると思います。名前がそのシステムでユニークになるような名前にする必要がありますが、長い文字列で共通にしてしまえば(偶然に他アプリと一致することは稀なので)正しく待機できると思います。

    ただ・・・大丈夫だとは思いますがbeginthread関連で実装したことがないので、beginthread内でCreateMutexやWaitFor等が動作するか分かりません。

    MutexのHandleそのものを別のスレッドで必要になるシチュエーションが思い浮かばないのですが、もしも必要な場合はちょっと悩ましいですね。

    • 回答としてマーク skrs 2012年3月9日 17:47
    2012年3月9日 10:58
  • 本筋ではないですが。

    Win32APIであれば、CreateMutexで名前付きオブジェクトを作成し、これをWaitForSingleObjects等で待たせることになると思います。名前がそのシステムでユニークになるような名前にする必要がありますが、長い文字列で共通にしてしまえば(偶然に他アプリと一致することは稀なので)正しく待機できると思います。

    同じプロセス内でなら、無名の Mutex なり、CriticalSection なりで十分だと思います。
    起動時にハンドルを作って、それが空くのを待てばよいだけでしょう。

    # 名前付きで実現するのはできなくもないですが、手段としてはちょっと行き過ぎな印象です。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。

    • 回答としてマーク skrs 2012年3月9日 17:47
    2012年3月9日 15:32
    モデレータ
  • Connect のフィードバックを眺めてると 「VS11 で対応する。」と答えてるので原因が同じなら直ってるかも?

    と思い立ち VS11 Developer Preview で再現コードをためしてみると再現しませんでした。

     VS11 で直りそうなのでバグなのでしょう。

    そうなると問題は回避策となりますが Connect の回避策を見る限りメモリ処理に不具合があるように思えます。

     同期処理に話が流れてるようですが、それで回避できればよいですけど・・・。

    • 回答としてマーク skrs 2012年3月9日 17:55
    2012年3月9日 17:15
  • kyano30様、BlueSkyColors様、Azulean様

    回答ありがとうございます。

    「スレッドセーフ」のようで、うまく動かないというのは少しもやもやするところはありますが、

    当座名前付きMutexを用いて、ロックを行うことで回避することにしました。一応クラッシュは発生しなくなった模様です。

    #元々稀にしか発生していない現象だったので、完全に直っているかは不明ですが。再現コードでは発生しなくなったようなので良しとしておきます。

    ライブラリ側 のバグと考えられるのならと思って Microsoft Connect サイトを検索すると似たようなバグ報告が見つかりました。

    フィードバックの回避策などを参照してみると良いかも知れません。

    リンクありがとうございます。

    Microsoft Connectは失念していました。

    こちらのほうはフィードバックの送信を含めて考えます。

    ただ・・・大丈夫だとは思いますがbeginthread関連で実装したことがないので、beginthread内でCreateMutexやWaitFor等が動作するか分かりません。

    一応過去に製造したものでは、beginthread内でMutexを使ったことがあり、問題は起きていませんのでおそらく大丈夫では無いかと思います。

    ちなみにCのRuntimeやC+のRuntimeを多様しているのでWin32APIのCreateThreadを使うのは避けています。

    同じプロセス内でなら、無名の Mutex なり、CriticalSection なりで十分だと思います。

    起動時にハンドルを作って、それが空くのを待てばよいだけでしょう。

    名前付きMutexにした理由は、既存の使用箇所が結構奥まったところにあるのでMutexのハンドルやCriticalSectionをそこまで引き回すのが手間だったからです。

    #グローバル変数にするのもありですが、個人的には避けたかったので。


    一先ず、クラッシュの回避方法はわかりましたので回答をつけさせていただきます。

    忙しい中ありがとうございました。

    2012年3月9日 17:47
  • 返信書いている間に再度返信が。

    VS11で直りそうですか!貴重な情報ありがとうございます!!

    VS11 Developer Previewはまだ試していないので、今度試してみます。

    #買えるかどうかは微妙ですが。VS2010今年買ったばっかり・・・。

    2012年3月9日 17:54