マルチスレッドや非同期処理を扱っていると、「ただの counter++ ですら安全ではない」という話にぶつかります。シングルスレッドでは何の問題もない単純な変数更新が、複数のスレッドが絡んだ瞬間に壊れることがあります。
そのような場面で使うのが Interlocked クラスです。
これは 複数スレッドから共有される変数を、ロックなしで安全に更新するための仕組み です。
Interlockedとは何か
一言でいうと、 変数操作を途中で割り込まれない形で一発でやるためのクラス です。
ここで重要なのが「アトミック(不可分)操作」という考え方です。 つまり、途中で別スレッドに割り込まれず、ひとかたまりとして実行される操作です。
なぜ必要なのか
たとえば、次のようなインクリメントは一見安全に見えます。
counter++;
しかし実際には、これは1命令ではありません。概念的には、
- 値を読む
- 1足す
- 書き戻す
という3段階です。
この間に別スレッドが割り込むと、結果が壊れます。
Thread A: read 0
Thread B: read 0
Thread A: write 1
Thread B: write 1
本来は 2 になるべきところが、結果は 1 になります。 これが race condition の典型です。
基本の使い方
こうした問題に対して、Interlocked を使うと更新をアトミックにできます。
Interlocked.Increment(ref counter);
これによって、
- 読み取り
- 加算
- 書き込み
がひとまとまりで実行されます。
よく使うメソッド
Increment / Decrement
Interlocked.Increment(ref counter);
Interlocked.Decrement(ref counter);
カウンタ更新の基本です。
Add
Interlocked.Add(ref counter, 5);
一定量だけ増減させたいときに使います。
Exchange
Interlocked.Exchange(ref value, newValue);
値を一気に入れ替えます。旧値も戻り値で得られます。
これはフラグ制御などで便利です。
if (Interlocked.Exchange(ref _flag, 1) == 0)
{
// 初回だけ通す
}
CompareExchange
これは最重要メソッドです。
Interlocked.CompareExchange(ref value, newValue, expectedValue);
意味は、 今の値が expectedValue なら newValue に置き換える です。
これは Compare-And-Swap(CAS)と呼ばれる代表的な原子的操作です。
典型例:一度だけ実行したい
if (Interlocked.CompareExchange(ref _initialized, 1, 0) == 0)
{
Initialize();
}
このコードは「まだ初期化されていないなら、自分が初期化担当になる」という意味です。
単純な if (_initialized == 0) ではレースになりますが、CompareExchange ならその判定と更新を一体で行えます。
lockとの違い
Interlocked と lock はどちらも同期のための道具ですが、向いている場面が違います。
| 項目 | Interlocked | lock |
|---|---|---|
| オーバーヘッド | 小さい | 大きい |
| 単純な値操作 | 得意 | 使えるがやや大げさ |
| 複雑な処理全体の保護 | 不向き | 得意 |
つまり、
- 単純な1変数操作 →
Interlocked - 複数行の複雑な排他 →
lock
という使い分けになります。
Interlockedでは守れないもの
ここは重要です。Interlocked は万能ではありません。
たとえば:
if (counter == 0)
{
counter++;
}
これは判定と更新が分かれているので、単なる Increment では守れません。
この場合は、CompareExchange のようなメソッドで「条件付き更新」をアトミックに表現する必要があります。
また、参照型の複雑な状態全体を保護する用途にも向きません。
Interlocked が保証するのは基本的に1変数単位です。
メモリ可視性も重要
Interlocked は単に原子的に更新するだけでなく、メモリバリアも伴います。
つまり、他スレッドから見たときの可視性も保証されます。
この点は、マルチコアCPUやキャッシュの存在を考えるとかなり重要です。単純に「一発で更新する」だけではなく、「その更新が他のスレッドにもちゃんと見える」ことまで面倒を見てくれます。
典型的な使いどころ
向いている用途
- カウンタ
- フラグ切り替え
- 初回だけ実行
- 軽量なスレッド同期
たとえばログ件数、アクセス件数、処理中フラグなどは典型です。
まとめ
Interlocked は、複数スレッドから共有される単純な変数を、安全かつ軽量に更新するためのクラスです。counter++ のような一見単純な操作でも、マルチスレッドでは壊れることがあります。そうした場面で、ロックを使わずにアトミックな更新を実現できます。
押さえておきたいポイント
- 単純な1変数操作に強い
IncrementとCompareExchangeが特に重要- 複雑な排他には向かない
- 可視性の保証もある
「共有変数を安全に更新したいが、lock をかけるほどではない」という場面では、まず Interlocked を思い出すとよいと思います。