思うだけで学ばない日記 2.0

思うだけで学ばない日記から移転しました☆!よろしくお願いします。

C#でマルチスレッド

C#において、参照形の代入は、参照のコピーのみ行われ、それが指すオブジェクト実体のコピーを伴わない。このことは、マルチスレッドプログラミングの際めんどくさい問題を引き起こす。スレッドA内の変数aがあるオブジェクトXへの参照であるとして、aをスレッドB内の変数bへ、単にb=a;として代入した場合、スレッドA, Bがそれぞれ変数a,bを経由して同一のオブジェクト実体Xへの参照を保持したままとなる。すると、スレッドBのコード上で、bの操作をクリティカルセクションでいかに一生懸命ガードしても、同じロックオブジェクト(=モニタ(?))を共有しないAがその間も自由に動いて変数a経由でXを変更し得るから、BがXを排他的に操作する保証を得られない。
じゃあどうするか?世間的には次のどれかような感じっぽい

  1. X操作時は必ず同一のロックオブジェクト(=モニタ(?))でクリティカルセクションに入るようにする
  2. 破壊的代入をやめる。スレッド間を渡るオブジェクトは作ったら一切変更できないものに限定する。
  3. X自体にX内部へのアクセスをクリティカルセクションで全部ガードさせる。
  4. AからBへは、オブジェクトXの代わりにXのディープコピーX'を渡す。

ちょっと考えたらわかるが1.はすぐ破綻する。誰かがいつかどこかでXの操作をクリティカルセクションでガードするのを忘れたとしてもコンパイラは何の警告もしてくれない。そんな低サポートの状況下であるにもかかわらず、Xへの参照箇所は、不用意な変数の代入で容易に増えていく。こんなもん管理不能
2.は少しマシな気がするが、C#においては破壊的代入可能なクラスのオブジェクトをスレッドをまたいで渡してしまったとしてもやはりコンパイラは何の警告もしないからフェイルプルーフの観点からは困難が大きい。まあ可能な限りimmutableにせよというのは同意するにしても、既存のクラスはだいたい破壊的なメソッドを有してしまっているから安全じゃないし、とわいえわざわざスレッド間のデータ受け渡しのためだけに専用のimmutableなクラスを追加で作るのもアホらしい気のする
3.が良いと思えるのは錯覚。Xの内部に参照型が紛れ込んで外と内通する限り、問題の解決にならない。
というわけで、4.を選択してみまくりんぐ


しかしC#において手軽なオブジェクトのディープコピー方法がなかなか見つからない。
「C# ArrayList のコピーについて」(1) Insider.NET − @IT
このリンクの最後の方で、ようやくBinaryFormatterでメモリにシリアライズ→デシリアライズするという荒技が示される。*1


今日わ、こうやって作ったオブジェクトのコピーをReaderWriterLockして安全に渡すクラステンプレートを書いてみたvv
ReaderWriterLockをクラステンプレートの外から隠蔽しようとするとX→X'というコピーにならなくて、どうしてもX→M→X'という2段階コピーになってしまうがorz


【今日の記事の元ネタ】
ttp://kaoriha.org/nikki/archives/000407.html
(いや紹介されてる本は買っても読んでもなくて、それについて書かれた記事本文がネタ。)

*1:これ以外というとリフレクションで無理矢理(一般性とはほど遠い制限だらけの)コピーメソッドを作るぐらい?