本文へスキップ
BecomeCoder

C#実践コース · 第1章 非同期プログラミング · レッスン7

並行処理とスレッド安全

ブラウザで完結

導入

複数のタスクが同じデータを同時に書き換えると、更新がぶつかって値が壊れます(競合状態)。これを防ぐのがlock(一度に1つだけ通す)・Interlocked(軽量な原子操作)・並行コレクション(ConcurrentDictionary)です。並行処理を安全に書くための基本装備を押さえます。

図解

flowchart TB
    T1["タスクA counter++"] --> G{"lock(1つずつ通す)"}
    T2["タスクB counter++"] --> G
    G --> C["counter を安全に更新"]
    style G fill:#fff3e0

サンプル

// 100個のタスクが同じ counter を増やす。lock で「一度に1つだけ」を保証
int counter = 0;
object gate = new object();

var tasks = new List<Task>();
for (int i = 0; i < 100; i++)
{
    tasks.Add(Task.Run(() =>
    {
        lock (gate)      // ここは一度に1タスクしか入れない
        {
            counter++;
        }
    }));
}
await Task.WhenAll(tasks);
Console.WriteLine(counter);   // 100(lockのおかげで数え落とさない)

// Interlocked: 単純な増減なら lock より軽い原子操作
int atomic = 0;
Interlocked.Increment(ref atomic);
Console.WriteLine(atomic);    // 1

// ConcurrentDictionary: 複数スレッドから安全に読み書きできる辞書
var dict = new System.Collections.Concurrent.ConcurrentDictionary<string, int>();
dict["a"] = 1;
Console.WriteLine(dict["a"]); // 1
  • 複数タスクが同じ変数を書き換えると競合状態で値が壊れる
  • lock (何かのオブジェクト) { ... }で「一度に1つのタスクだけ」を保証する
  • 単純なカウントはInterlocked.Increment/Addが軽い。共有辞書はConcurrentDictionary
  • ロックの掛けすぎは遅くなる・デッドロックの元。共有をなるべく減らす設計が最善

演習

int value = 0;

// TODO: Interlocked.Increment を使って value を 1 増やしてください
___

Console.WriteLine(value);
  • 期待される出力: 1
ヒント1を見る

Interlocked.Increment(ref value);valueを原子的に+1します

ヒント2を見る

refを付けてInterlocked.Increment(ref value);

まとめ

  • 共有データの同時更新は競合状態を招く
  • lockで排他、単純な数え上げはInterlocked、共有辞書はConcurrentDictionary
  • そもそも共有を減らす設計がいちばん安全

次回: 非同期に1件ずつ届くデータを扱うIAsyncEnumerableです。

実際に動かしてみよう

本文のサンプルや演習のコードは、コードブロック右上の「コピー」ボタンでコピーして、下のエディタに貼り付ければそのまま実行できます。

C# — ブラウザ内で実行

ブラウザ内でC#を動かす環境を読み込みます(初回のみ数秒)。
スクロールして表示された時点でも自動で読み込まれます。