none
Boolean変数をスレッドセーフに設定する方法について RRS feed

  • 質問

  • よろしくお願いいたします。

     現在、2つのスレッドで共有するBoolean変数を、どのように設定するかについて考えております。

    最初は、lockステートメントを利用して、スレッドセーフにしていたのですが、Interlockedクラスを使えば、コストが小さいとのことなのですが、Booleanに対するオーバーライドが無いため、利用できません。あとはvolatile宣言を使ってみようかとも考えています。

    そこで、コメントをいただきたいのですが、Boolean変数をスレッドセーフに設定・取得するためには、どのようにするのが最適でしょうか?

    大変恐縮ではございますが、ご教授いただけると幸いです。よろしくおねがいします。

    2006年10月21日 8:25

回答

  • コストについてはよくわかりませんが、lock でいいんじゃないかと思います。

    volatile はヘルプの通り、最適化を抑制することで常に最新の値が得られるようになるだけですので、例えば次のような操作は volatile を指定したとしてもスレッドセーフにはなりません。
       boolField = !boolField;
    中断フラグのように、最新の値の監視が目的であれば良いですが。

    Interlocked クラスは bool は想定されていませんね。
    ただ普通には使えませんが、上記のような反転処理の場合だけを想定すれば、Increment して偶数かどうかで bool として扱うという方法もなくはないかと思います。(^^;

    2006年10月21日 11:01
  • C#でしたら、
    (Patterns & Practicies)C# でのシングルトンの実装
    http://www.microsoft.com/japan/msdn/practices/type/Patterns/enterprise/impsingletonincsharp.aspx

    のマルチスレッド・シングルトン
    のコードを参考に改良されるのもよいかと。
    新規につくるよりは安心感があると思います。

    CLRがダブルチェックロッキングの問題を解決してくれるってかいてありますね。

    ダブルチェックロッキングは以下を参考にするといいかも
    http://himtodo.fc2web.com/java/doubleCheck.html
    http://www-06.ibm.com/jp/developerworks/java/020726/j_j-dcl.html

    2006年10月22日 17:09

すべての返信

  • コストについてはよくわかりませんが、lock でいいんじゃないかと思います。

    volatile はヘルプの通り、最適化を抑制することで常に最新の値が得られるようになるだけですので、例えば次のような操作は volatile を指定したとしてもスレッドセーフにはなりません。
       boolField = !boolField;
    中断フラグのように、最新の値の監視が目的であれば良いですが。

    Interlocked クラスは bool は想定されていませんね。
    ただ普通には使えませんが、上記のような反転処理の場合だけを想定すれば、Increment して偶数かどうかで bool として扱うという方法もなくはないかと思います。(^^;

    2006年10月21日 11:01
  • C#でしたら、
    (Patterns & Practicies)C# でのシングルトンの実装
    http://www.microsoft.com/japan/msdn/practices/type/Patterns/enterprise/impsingletonincsharp.aspx

    のマルチスレッド・シングルトン
    のコードを参考に改良されるのもよいかと。
    新規につくるよりは安心感があると思います。

    CLRがダブルチェックロッキングの問題を解決してくれるってかいてありますね。

    ダブルチェックロッキングは以下を参考にするといいかも
    http://himtodo.fc2web.com/java/doubleCheck.html
    http://www-06.ibm.com/jp/developerworks/java/020726/j_j-dcl.html

    2006年10月22日 17:09
  • コメントありがとうございました。

    無理にInterlockedクラスを利用せず、lockステートメントを使った手法を考えてみたいと思います。

    2006年10月23日 2:16
  •  もんた さんからの引用

    最初は、lockステートメントを利用して、スレッドセーフにしていたのですが、Interlockedクラスを使えば、コストが小さいとのことなのですが、Booleanに対するオーバーライドが無いため、利用できません。あとはvolatile宣言を使ってみようかとも考えています。

    そこで、コメントをいただきたいのですが、Boolean変数をスレッドセーフに設定・取得するためには、どのようにするのが最適でしょうか?

    流石にきちんと把握できていない技術でスレッドセーフを実現しても,デバッグなどで困るでしょうから,きちんと理解できている技術で十分なパフォーマンスが得られているならそれが最善だと思いますよ.

    私なら,スレッド間の同期に使うようなフラグであれば,変数型よりも実利を優先して bool から int に変更し,Interlocked クラスを使用します.
    ただし,そこまで処理速度がクリティカルでない状況でわざわざ Interlocked クラスを使って苦労する暇があれば,先にマルチスレッド環境での動作検証をするためのテストを整備する方が有益でしょう.

    2006年10月23日 2:17
  • コストについてはわからないと私は書きましたが、それへの一般的な回答が "double-checked locking" なんですね。おぎわらさんのリンク、大変勉強になりました。
    最初はなぜ JAVA でのシングルトンの話?、と思いましたが、単純に JAVA でこの仕組みを活用すると、JAVA のメモリー・モデルとの相性により問題が生じる場合があるけど、.NET では大丈夫、ということですね(JAVA の方も単純なバグという訳ではないですね)。

    ただ1点、リンク先のマイクロソフトの文章についてですが、
    「さらに、変数が volatile として宣言されていることで、インスタンス変数への代入が完了するまでは、インスタンス変数にアクセスできなくなっています」
    という一文がありますが、語弊があるように思います。
    これでは volatile によってロックが発生する(もしくはロックが解除されるまで待機する)かのようにとれますが、volatile とロックは何の関係もないですよね...? 私は、volatile は実装によっては必須ではないと思っています。

    今回、いろいろな検証コードを書いたのですが、NyaRuRu さんが書かれた「マルチスレッド環境での動作検証をするためのテストを整備」も難しそうですね。

    2006年10月23日 4:19
  •  TH01 さんからの引用

    コストについてはわからないと私は書きましたが、それへの一般的な回答が "double-checked locking" なんですね。おぎわらさんのリンク、大変勉強になりました。
    最初はなぜ JAVA でのシングルトンの話?、と思いましたが、単純に JAVA でこの仕組みを活用すると、JAVA のメモリー・モデルとの相性により問題が生じる場合があるけど、.NET では大丈夫、ということですね(JAVA の方も単純なバグという訳ではないですね)。

    Monitor クラス (が内部で使用している Win32 クリティカルセクション) も,SpinLock による Lock-Free アルゴリズムを使用する場合があり,必ずしも Interlocked クラスが圧倒的に有利とは限りません.

    また,Java の "double-checked locking" の話は歴史的に有名ですが,C# + CLR にも次のような問題点は残っています.

    • C# の using 構文を使用すると,ごく希にリソース回収が次回の GC まで遅延することがあります.
    • C# の lock 構文を使用すると,ごく希にlock 解放が次回の GC まで遅延することがあります.

    とはいえ発生確率や発生条件はかなり限定的なため,通常これらの問題が議論されることはなくて,十中八九は「using 構文なり lock 構文なりを使いましょう」という回答がなされることになります.
    以前手元の環境で試したときは,非常に恣意的な状況を作って発生確率 0.02 % ぐらいでした (実際の環境ではその何桁も発生確率は下がるでしょう).
    今回の件でも,よほどシビアな環境 (非同期例外が回避不能なロジックを,大量の計算機でクラスタリングさせるとか,非常に長期間安定稼働が必要とか) でないかぎり,lock なり何なりで十分だとは思います.

    通常のアプリケーション開発の範囲内では不要かもしれませんが,このような事情に興味があるのでしたら,拙文ですが以下の記事などをご参照下さい.
    http://d.hatena.ne.jp/NyaRuRu/20060605
    http://d.hatena.ne.jp/NyaRuRu/20060721

    2006年10月23日 4:43
  •  NyaRuRu さんからの引用
    通常のアプリケーション開発の範囲内では不要かもしれませんが,このような事情に興味があるのでしたら,拙文ですが以下の記事などをご参照下さい.
    http://d.hatena.ne.jp/NyaRuRu/20060605
    http://d.hatena.ne.jp/NyaRuRu/20060721


    このような為になるリンクがすぐに提示でき、しかもそれがご自分で書かれたものというのはすごいですね!

    using ステートメントの方ですが、

    using (ResourceType resource = expression) statement

    が、もし

    ResourceType resource = null;
    try
    {
        resource = expression;
        statement;
    }
    finally
    {
        if (resource != null)
            ((IDisposable)resource).Dispose();
    }

    と等価になれば問題は起きないと考えていいでしょうか?
    そうであれば、コンパイラが少し解釈を変えれば良いのかなぁと思います。
    lock の方も、問題が発生するメカニズムは同じということですね?

    # もんたさんのスレッド乗っ取り中...(^^;

    2006年10月23日 5:54
  •  TH01 さんからの引用

    ResourceType resource = null;
    try
    {
        resource = expression;
        statement;
    }
    finally
    {
        if (resource != null)
            ((IDisposable)resource).Dispose();
    }

    と等価になれば問題は起きないと考えていいでしょうか?
    そうであれば、コンパイラが少し解釈を変えれば良いのかなぁと思います。
    lock の方も、問題が発生するメカニズムは同じということですね?

    残念ですがコンパイラレベルでは対応できません.
    仮に,expression が戻り値で結果を返すメソッドではなく,out 引数で結果を返すメソッドであれば,問題を回避する余地はあります.
    実際,記事で紹介した blog では,CLR 2.0 がこのような方式で lock を行っていることが示されています.
    そういう意味では,Base Class Library (BCL) の設計上の問題です.

    根本的な問題は,resource が null かどうかと,expression が評価されたかどうかが,一致しなくなる瞬間が存在するということです.
    expression が評価された直後に非常にタイミング悪く非同期例外が発生した場合を考えてみてください.
    resource 変数が null のまま finally 句に突入してしまいます.
    この場合 resource 変数は null のままですから,当然 Dispose は呼ばれません.

    CLR 2.0 では Constrained Execution Regions を使用して,非同期例外の発生を事前に確認するという方法もあります.
    詳細については MSDN マガジン オンラインの記事 (要登録) の『.NET Framework の信頼性機能でコードを実行し続ける』 等を参照してください.

    2006年10月23日 6:37
  •  NyaRuRu さんからの引用
    根本的な問題は,resource が null かどうかと,expression が評価されたかどうかが,一致しなくなる瞬間が存在するということです.
    expression が評価された直後に非常にタイミング悪く非同期例外が発生した場合を考えてみてください.
    resource 変数が null のまま finally 句に突入してしまいます.

    確かに仰るとおりです...。
    リンク先にもそのままのことが書かれていますね。浅はかでした。(^^;

    ただ、一通り目を通して理解できたのは1割もありませんでした...。
    using の場合、私が書いたコードの try の前に
    RuntimeHelpers.PrepareConstrainedRegions();
    が勝手につくようになれば良いのだろうか...、しかしそれではコストが高くついてしまいそう...、などと思いながら読んだのですが、結局、どのように修正される予定なのかなどはわかりませんでした。
    NyaRuRu さんが書かれたように、まず、これが問題になることはないのでしょうから、安易に修正・対応しない、というのが最善なのかなぁと思ったり。

    # しかしまた、すごく当を得たリンクでビックリ。私には馬の耳に念仏状態でしたが(^^;

    2006年10月23日 8:50