「++」または「--」操作で単一の Int32 変数にアクセスする複数のスレッドがあります。
以下のように、アクセスする前にロックする必要がありますか?
lock (idleAgentsLock)
{
idleAgents--; //we should place it here.
}
または、ロックを行わないと、どのような結果が生じるでしょうか?
マルチスレッドの意味では「アトミック」ではありません。ただし、少なくとも理論的には、ロックは操作を保護します。疑わしい場合は、もちろんInterlocked.Decrement
代わりに使用できます。
いいえ ++/-- はアトミック操作ではありませんが、整数やその他のプリミティブ時間の読み取りと書き込みはアトミック操作と見なされます。
次のリンクを参照してください。
また、このブログ投稿方法は、あなたにとって興味深いものです。
++/-- 演算子のアトミックな実装には、InterlockedクラスのInterlocked.Incrementをお勧めします。
保護されていないインクリメント/デクリメントはスレッドセーフではないため、スレッド間でアトミックではありません。(ただし、実際の IL/ML 変換に関しては「アトミック」である可能性があります1。)
この LINQPad のサンプル コードは、予測できない結果を示しています。
void Main()
{
int nWorkers = 10;
int nLoad = 200000;
int counter = nWorkers * nLoad;
List<Thread> threads = new List<Thread>();
for (var i = 0; i < nWorkers; i++) {
var th = new Thread((_) => {
for (var j = 0; j < nLoad; j++) {
counter--; // bad
}
});
th.Start();
threads.Add(th);
}
foreach (var w in threads) {
w.Join();
}
counter.Dump();
}
スレッド間の可視性が重要であることに注意してください。同期は、原子性に加えてこの可視性を保証します。
このコードは、少なくとも提示された限られたコンテキストでは、簡単に修正できます。デクリメントを切り替えて、結果を観察します。
counter--; // bad
Interlocked.Decrement(ref counter); // good
lock (threads) { counter--; } // good
1volatile
変数を使用しても、結果は予測できません。これは、競合するスレッドの読み取り/操作/書き込みがインターリーブされていたため、(少なくともここで実行したときは) アトミック オペレーターでもないことを示しているようです。可視性の問題が取り除かれても動作が正しくないことを確認するには (そうですか?)、次のように追加します。
class x {
public static volatile int counter;
}
上記のコードを変更してx.counter
、ローカルcounter
変数の代わりに使用します。
マシンのアーキテクチャに依存します。一般に、いいえ、コンパイラはロード命令を生成し、値をインクリメント/デクリメントしてから格納する場合があります。そのため、他のスレッドがこれらの操作の間に実際に値を読み取る可能性があります。
ほとんどの CPU 命令セットには、この目的のための特別なアトミック テスト & セット命令があります。アセンブリ命令を C# コードに埋め込みたくない場合、次善の方法は、示したものと同様に、相互排除を使用することです。そのメカニズムの実装では、最終的に、アトミックな命令を使用してミューテックス (またはミューテックスが使用するもの) を実装します。
要するに、はい、相互排除を確保する必要があります。
この回答の範囲を超えて、状況のドメインロジックに応じて適切または適切ではない共有データを管理するための他の手法があります。