現在、コピー オン ライト セットの実装を検討しており、それがスレッド セーフであることを確認したいと考えています。そうでない可能性がある唯一の方法は、コンパイラーが特定のメソッド内でステートメントを並べ替えることが許可されている場合であると確信しています。たとえば、Remove
メソッドは次のようになります。
public bool Remove(T item)
{
var newHashSet = new HashSet<T>(hashSet);
var removed = newHashSet.Remove(item);
hashSet = newHashSet;
return removed;
}
hashSet が次のように定義されている場所
private volatile HashSet<T> hashSet;
だから私の質問は、 hashSet がメンバー変数への書き込みの前に新しいセットで発生するvolatile
ことを意味するのでしょうか? Remove
そうでない場合、削除が行われる前に他のスレッドがセットを参照する可能性があります。
本番環境でこれに関する問題は実際に見たことがありませんが、安全であることが保証されていることを確認したいだけです.
アップデート
より具体的には、を取得する別の方法がありますIEnumerator
。
public IEnumerator<T> GetEnumerator()
{
return hashSet.GetEnumerator();
}
したがって、より具体的な質問は次のとおりです。返されたものが削除からIEnumerator
決してスローしないという保証はありますか?ConcurrentModificationException
更新 2
申し訳ありませんが、回答はすべて複数のライターからのスレッドセーフに対処しています。良い点が挙げられていますが、それは私がここで見つけようとしているものではありません. コンパイラが操作を次のように並べ替えることができるかどうかを知りたいですRemove
。
var newHashSet = new HashSet<T>(hashSet);
hashSet = newHashSet; // swapped
var removed = newHashSet.Remove(item); // swapped
return removed;
これが可能である場合、割り当てられたGetEnumerator
後、削除される前にスレッドが呼び出すことができることを意味し、列挙中にコレクションが変更される可能性があります。hashSet
item
Joe Duffy には、次のようなブログ記事があります。
ロード時に揮発性とは、ACQUIRE を意味し、それ以上でもそれ以下でもありません。(もちろん、ループ外で巻き上げを許可しないなど、追加のコンパイラー最適化制限がありますが、ここでは MM の側面に焦点を当てましょう。) ACQUIRE の標準的な定義は、後続のメモリ操作が ACQUIRE 命令の前に移動しない可能性があることです。たとえば、{ ld.acq X, ld Y } が与えられた場合、ld Y は ld.acq X の前に発生することはできません。たとえば、{ ld X, ld.acq Y } の場合、ld.acq Y は実際に ld X の前に発生する可能性があります。これが実際に発生する唯一のプロセッサ Microsoft .NET コードが現在実行されているのは IA64 ですが、これは注目すべき領域です。 CLR の MM は、ほとんどのマシンよりも弱いです。次に、.NET 上のすべてのストアは RELEASE です (揮発性に関係なく、つまり、揮発性は jitted コードの観点からノーオペレーションです)。RELEASE の標準的な定義は、以前のメモリ操作は RELEASE 操作の後に移動できないというものです。たとえば、{ st X, st.rel Y } が与えられた場合、st.rel Y は st X の前に発生することはできません。たとえば、{ st.rel X, ld Y } が与えられた場合、ld Y は st.rel X の前に移動できます。
私がこれを読む方法は、 への呼び出しにnewHashSet.Remove
は が必要でld newHashSet
あり、 への書き込みにhashSet
は が必要であるということst.rel newHashSet
です。上記の RELEASE の定義から、ストア RELEASE の後にロードを移動することはできないため、ステートメントを並べ替えることができません。私の解釈が正しいことを確認してください。