トップ回答者
Dictionaryをスレッドセーフにするには

質問
-
お世話になります。mkmarimoです。
Dictionaryコレクションをスレッドセーフにする一般的な方法ってあるのでしょうか。
現在、プログラム開始時に、とあるXMLファイルの情報を別スレッドでDictionaryコレクションに
格納するというコードを書いています。
どうもこの別スレッドの処理が遅いようで、スレッドを開始した直後にDictionaryコレクションに
インデクサでアクセスするとKeyNotFoundExceptionがスローされてしまいます。
※Dictionaryコレクションへのアクセスをしばらく待てば正常に動作します。
そこで、ReaderWriterLockを利用してDictionaryコレクションへの
・値の追加、更新時 → ライターロックを取得、終われば解放
・値の読み出し時 → リーダーロックを取得、終われば解放
というコードを書いています。
しかし、どうもうまく動作せず、やはり上記と同じコードでKeyNotFoundExceptionがスローされます。
Dictionaryコレクションをスレッドセーフにする方法を、結構調べてみたのですが、
有効な情報が得られませんでしたので投稿させていただきました。
ReaderWriterLockのことについてでも結構ですので、ご教授お願いします。
回答
-
しかし、引き続き本質問はオープンさせていただきますので、
Dictionaryコレクションをスレッドセーフにするもっと一般的な方法があればお教えください。
一般的(普遍的)な方法はReaderWriterLockです。もっと普遍的な方法ありません。
一般的(日常的)には、Dictionaryがスレッドセーフで無くてもよいように、プログラムのデザインを考え直します。
ドキュメントにあるように、Dictionaryはハッシュテーブルで実装されています。
ハッシュテーブルや配列など、一般的なデータ構造は
読み取りは共有、書き込みは排他ロックで
スレッドセーフにできるように実装することが可能です。
Dictionaryもそのように作られています。
ですので、ReaderWriterLockで同期を取るので正解です。
今現在気になっているのは、Dictionaryクラスのヘルプの「明示的インタフェースの実装」という項目の、
"System.Collections.ICollection.SyncRoot"
という部分です。
囚人さんのコードにあるように、SyncRootを使う場合は
読み取りも書き込みも排他ロックになりますので、教科書的には推奨されません。
同期の頻度によりますが、実際にはReaderWriterLockとあまり変わらない速度になることも多いです。
先も書いたように、一般的なデータ構造は全てReaderWriterLockでスレッドセーフにできます。
データ構造によってはもっといい方法がある場合もありますが、実装が大変なので普通は使いません。
Queueは例外で、簡単にスレッドセーフなものを作れます。
速度も高速ですので、自分で実装して使う場合もあります。
LinkedListやTrieなども、スレッドセーフなものをたまに見かけます。
#残念ながら標準のQueueなどはReaderWriterLockで同期制御しなければいけません。
すべての返信
-
囚人さん
お世話になります。mkmarimoです。
囚人 さんからの引用 どんなコードを書いているんでしょうか?ReaderWriterLockの使い方は間違いなくあってますか?
質問投稿後、しばらく切り分け作業を行ったところ、原因がわかりました。
どうやら、はじめにDictionaryコレクションに値を登録するときのライターロックがかかるより先に、
インデクサによるDictionaryコレクションへのアクセスが発生してしまったようです。
お騒がせしてすみません。
しかし、引き続き本質問はオープンさせていただきますので、
Dictionaryコレクションをスレッドセーフにするもっと一般的な方法があればお教えください。
今現在気になっているのは、Dictionaryクラスのヘルプの「明示的インタフェースの実装」という項目の、
"System.Collections.ICollection.SyncRoot"
という部分です。
この部分が気になり、説明を読みましたが、知識不足のため内容を理解することができませんでした。
インタフェースすら完全に理解できてません。。。
"System.Collections.ICollection.SyncRoot"はなにかスレッドセーフ実現に関係あるのでしょうか。
ご存じの方、ご教授お願いします。
-
mkmarimo さんからの引用 現在、プログラム開始時に、とあるXMLファイルの情報を別スレッドでDictionaryコレクションに
格納するというコードを書いています。
わざわざ、別スレッドにする理由がよくわからないのですが、
「ユーザは、必ずしも、このXMLファイルの情報にアクセスするとは限らない。」ということでしょうか?
であれば、「このXMLファイルの情報」へのアクセスを必要とするメニューなり、ボタンなりを、別スレッドが終了するまで、ディセーブルにすればよいのでは?
お話を聞く限り、「このXMLファイルの情報」へアクセスしたら最後、読み込みが終わるまで待ちぼうけを食わされるようですので、それはどうかと思うのですが。。
-
しかし、引き続き本質問はオープンさせていただきますので、
Dictionaryコレクションをスレッドセーフにするもっと一般的な方法があればお教えください。
一般的(普遍的)な方法はReaderWriterLockです。もっと普遍的な方法ありません。
一般的(日常的)には、Dictionaryがスレッドセーフで無くてもよいように、プログラムのデザインを考え直します。
ドキュメントにあるように、Dictionaryはハッシュテーブルで実装されています。
ハッシュテーブルや配列など、一般的なデータ構造は
読み取りは共有、書き込みは排他ロックで
スレッドセーフにできるように実装することが可能です。
Dictionaryもそのように作られています。
ですので、ReaderWriterLockで同期を取るので正解です。
今現在気になっているのは、Dictionaryクラスのヘルプの「明示的インタフェースの実装」という項目の、
"System.Collections.ICollection.SyncRoot"
という部分です。
囚人さんのコードにあるように、SyncRootを使う場合は
読み取りも書き込みも排他ロックになりますので、教科書的には推奨されません。
同期の頻度によりますが、実際にはReaderWriterLockとあまり変わらない速度になることも多いです。
先も書いたように、一般的なデータ構造は全てReaderWriterLockでスレッドセーフにできます。
データ構造によってはもっといい方法がある場合もありますが、実装が大変なので普通は使いません。
Queueは例外で、簡単にスレッドセーフなものを作れます。
速度も高速ですので、自分で実装して使う場合もあります。
LinkedListやTrieなども、スレッドセーフなものをたまに見かけます。
#残念ながら標準のQueueなどはReaderWriterLockで同期制御しなければいけません。
-
-
れいさん
ご回答ありがとうございます。mkmarimoです。
れい さんからの引用 一般的(普遍的)な方法はReaderWriterLockです。もっと普遍的な方法ありません。
一般的(日常的)には、Dictionaryがスレッドセーフで無くてもよいように、プログラムのデザインを考え直します。
・・・
ですので、ReaderWriterLockで同期を取るので正解です。
ReaderWriterLockで問題ないとのことで安心しました。
ここのウラが取れただけでも質問を投稿した意味がありました。
れい さんからの引用 囚人さんのコードにあるように、SyncRootを使う場合は
読み取りも書き込みも排他ロックになりますので、教科書的には推奨されません。
同期の頻度によりますが、実際にはReaderWriterLockとあまり変わらない速度になることも多いです。
SyncRootの動きは上記のように読み書き共に排他ロックになるのですね。
参考になります。
れいさん、ありがとうございました。
C#初心者ですので、また投稿させてもらうことがあると思います。
よろしくお願いします。