none
Dictionaryをスレッドセーフにするには RRS feed

  • 質問

  • お世話になります。mkmarimoです。

     

    Dictionaryコレクションをスレッドセーフにする一般的な方法ってあるのでしょうか。

     

    現在、プログラム開始時に、とあるXMLファイルの情報を別スレッドでDictionaryコレクションに

    格納するというコードを書いています。

     

    どうもこの別スレッドの処理が遅いようで、スレッドを開始した直後にDictionaryコレクションに

    インデクサでアクセスするとKeyNotFoundExceptionがスローされてしまいます。

    ※Dictionaryコレクションへのアクセスをしばらく待てば正常に動作します。

     

    そこで、ReaderWriterLockを利用してDictionaryコレクションへの

    ・値の追加、更新時 → ライターロックを取得、終われば解放

    ・値の読み出し時 → リーダーロックを取得、終われば解放

    というコードを書いています。

     

    しかし、どうもうまく動作せず、やはり上記と同じコードでKeyNotFoundExceptionがスローされます。

     

    Dictionaryコレクションをスレッドセーフにする方法を、結構調べてみたのですが、

    有効な情報が得られませんでしたので投稿させていただきました。

     

    ReaderWriterLockのことについてでも結構ですので、ご教授お願いします。

     

    2007年10月30日 9:29

回答

  • しかし、引き続き本質問はオープンさせていただきますので、

    Dictionaryコレクションをスレッドセーフにするもっと一般的な方法があればお教えください。

     

    一般的(普遍的)な方法はReaderWriterLockです。もっと普遍的な方法ありません。

    一般的(日常的)には、Dictionaryがスレッドセーフで無くてもよいように、プログラムのデザインを考え直します。

     

    ドキュメントにあるように、Dictionaryはハッシュテーブルで実装されています。

    ハッシュテーブルや配列など、一般的なデータ構造は

    読み取りは共有、書き込みは排他ロックで

    スレッドセーフにできるように実装することが可能です。

    Dictionaryもそのように作られています。

     

    ですので、ReaderWriterLockで同期を取るので正解です。

     

    今現在気になっているのは、Dictionaryクラスのヘルプの「明示的インタフェースの実装」という項目の、

    "System.Collections.ICollection.SyncRoot"

    という部分です。

     

    囚人さんのコードにあるように、SyncRootを使う場合は

    読み取りも書き込みも排他ロックになりますので、教科書的には推奨されません。

    同期の頻度によりますが、実際にはReaderWriterLockとあまり変わらない速度になることも多いです。

     

    先も書いたように、一般的なデータ構造は全てReaderWriterLockでスレッドセーフにできます。

    データ構造によってはもっといい方法がある場合もありますが、実装が大変なので普通は使いません。

    Queueは例外で、簡単にスレッドセーフなものを作れます。

    速度も高速ですので、自分で実装して使う場合もあります。

    LinkedListやTrieなども、スレッドセーフなものをたまに見かけます。

     

    #残念ながら標準のQueueなどはReaderWriterLockで同期制御しなければいけません。

    2007年10月30日 13:51

すべての返信

  • どんなコードを書いているんでしょうか?ReaderWriterLockの使い方は間違いなくあってますか?

    2007年10月30日 10:54
  • 囚人さん

     

    お世話になります。mkmarimoです。

     

     囚人 さんからの引用

    どんなコードを書いているんでしょうか?ReaderWriterLockの使い方は間違いなくあってますか?

     

    質問投稿後、しばらく切り分け作業を行ったところ、原因がわかりました。

     

    どうやら、はじめにDictionaryコレクションに値を登録するときのライターロックがかかるより先に、

    インデクサによるDictionaryコレクションへのアクセスが発生してしまったようです。

     

    お騒がせしてすみません。

     

     

    しかし、引き続き本質問はオープンさせていただきますので、

    Dictionaryコレクションをスレッドセーフにするもっと一般的な方法があればお教えください。

     

    今現在気になっているのは、Dictionaryクラスのヘルプの「明示的インタフェースの実装」という項目の、

    "System.Collections.ICollection.SyncRoot"

    という部分です。

    この部分が気になり、説明を読みましたが、知識不足のため内容を理解することができませんでした。

    インタフェースすら完全に理解できてません。。。

     

    "System.Collections.ICollection.SyncRoot"はなにかスレッドセーフ実現に関係あるのでしょうか。

    ご存じの方、ご教授お願いします。

     

    2007年10月30日 11:03
  •  mkmarimo さんからの引用

    現在、プログラム開始時に、とあるXMLファイルの情報を別スレッドでDictionaryコレクションに

    格納するというコードを書いています。

    わざわざ、別スレッドにする理由がよくわからないのですが、

    「ユーザは、必ずしも、このXMLファイルの情報にアクセスするとは限らない。」ということでしょうか?

    であれば、「このXMLファイルの情報」へのアクセスを必要とするメニューなり、ボタンなりを、別スレッドが終了するまで、ディセーブルにすればよいのでは?

    お話を聞く限り、「このXMLファイルの情報」へアクセスしたら最後、読み込みが終わるまで待ちぼうけを食わされるようですので、それはどうかと思うのですが。。

    2007年10月30日 11:25

  • "System.Collections.ICollection.SyncRoot"はなにかスレッドセーフ実現に関係あるのでしょうか。
    ご存じの方、ご教授お願いします。

    ドキュメントに書いてある通り

    コード ブロック
    Dictionary<X> d = new Dictionary<X>();
    ICollection c = d;
    lock(d.SyncRoot){
     // d を操作
    }

     

     


    のように、単に同期用のオブジェクトとして使うだけだと思います。

     

    2007年10月30日 12:25
  • しかし、引き続き本質問はオープンさせていただきますので、

    Dictionaryコレクションをスレッドセーフにするもっと一般的な方法があればお教えください。

     

    一般的(普遍的)な方法はReaderWriterLockです。もっと普遍的な方法ありません。

    一般的(日常的)には、Dictionaryがスレッドセーフで無くてもよいように、プログラムのデザインを考え直します。

     

    ドキュメントにあるように、Dictionaryはハッシュテーブルで実装されています。

    ハッシュテーブルや配列など、一般的なデータ構造は

    読み取りは共有、書き込みは排他ロックで

    スレッドセーフにできるように実装することが可能です。

    Dictionaryもそのように作られています。

     

    ですので、ReaderWriterLockで同期を取るので正解です。

     

    今現在気になっているのは、Dictionaryクラスのヘルプの「明示的インタフェースの実装」という項目の、

    "System.Collections.ICollection.SyncRoot"

    という部分です。

     

    囚人さんのコードにあるように、SyncRootを使う場合は

    読み取りも書き込みも排他ロックになりますので、教科書的には推奨されません。

    同期の頻度によりますが、実際にはReaderWriterLockとあまり変わらない速度になることも多いです。

     

    先も書いたように、一般的なデータ構造は全てReaderWriterLockでスレッドセーフにできます。

    データ構造によってはもっといい方法がある場合もありますが、実装が大変なので普通は使いません。

    Queueは例外で、簡単にスレッドセーフなものを作れます。

    速度も高速ですので、自分で実装して使う場合もあります。

    LinkedListやTrieなども、スレッドセーフなものをたまに見かけます。

     

    #残念ながら標準のQueueなどはReaderWriterLockで同期制御しなければいけません。

    2007年10月30日 13:51
  • Tom Yamaさん

     

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

     

     Tom Yama さんからの引用

    わざわざ、別スレッドにする理由がよくわからないのですが、

    「ユーザは、必ずしも、このXMLファイルの情報にアクセスするとは限らない。」ということでしょうか?

     

    別スレッドにした意図は、このXMLファイルを読み込む処理は時間がかかるが、

    メモリ上に読み込んだあと、すぐ使うとは限らないので、読み込むところは、バックグラウンドで

    やってしまおうと考えたからです。

     

    貴重なご意見ありがとうございました。

    今後、参考にさせていただきます。

    2007年10月31日 4:19
  • れいさん

     

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

     

     れい さんからの引用

    一般的(普遍的)な方法はReaderWriterLockです。もっと普遍的な方法ありません。

    一般的(日常的)には、Dictionaryがスレッドセーフで無くてもよいように、プログラムのデザインを考え直します。

    ・・・

    ですので、ReaderWriterLockで同期を取るので正解です。

     

    ReaderWriterLockで問題ないとのことで安心しました。

    ここのウラが取れただけでも質問を投稿した意味がありました。

     

     れい さんからの引用

    囚人さんのコードにあるように、SyncRootを使う場合は

    読み取りも書き込みも排他ロックになりますので、教科書的には推奨されません。

    同期の頻度によりますが、実際にはReaderWriterLockとあまり変わらない速度になることも多いです。

     

    SyncRootの動きは上記のように読み書き共に排他ロックになるのですね。

    参考になります。

    れいさん、ありがとうございました。

     

    C#初心者ですので、また投稿させてもらうことがあると思います。

    よろしくお願いします。

    2007年10月31日 4:24