ホームに戻る
出典 :
lock ステートメント - C# リファレンス - Microsoft Learn Semaphore クラス (System.Threading) | Microsoft Learn SemaphoreSlim クラス (System.Threading) - Microsoft Learn Mutex クラス (System.Threading) | Microsoft Learn C#のlockとは?意味や使い方とサンプルプログラムをご紹介 – Rainbow Engine マルチスレッドで高速なC#を書くためのロック戦略 - Qiita SemaphoreSlim を使って並列実行を制御する - Qiita [C#] 非同期 async/awaitを含むメソッドで lockする方法
関連 :
セマフォとミューテックス タスク(スレッド)間通信 アプリケーション多重起動の抑止 Taskの開始と終了待ち シグナル処理
目次 :

lock ステートメントによる排他制御

任意のオブジェクトを用いて簡便に相互排他を行うことができる。
あるスレッドがロックを取得している間に、他のスレッドが同一のオブジェクトを用いてロックを取得しようとした場合はブロックされ、先にロックを取得したスレッドがロックを解放するまで待ち状態となる。
機能としてはバイナリセマフォと同等である。後述のセマフォよりもパフォーマンス面で有利。

一般形

lock ステートメント中に、排他実行したい処理を記述する。
public class Sample { // ロックオブジェクト private object obj_A; // lock ステートメントを含む DoSomething() メソッド private void DoSomething() { // lock ステートメント lock( obj_A ) { // 排他実行したい処理 } } }

lock ステートメントと例外

lock ステートメントは以下のコードと等価である。
object __lockObj = obj_A; bool __lockWasTaken = false; try { System.Threading.Monitor.Enter(__lockObj, ref __lockWasTaken); // 排他実行したい処理 } finally { if (__lockWasTaken) System.Threading.Monitor.Exit(__lockObj); }
try - finally ブロックが使用されているため、例外が発生した場合でもロックが解放される(例外安全)。

注意すべき点

セマフォによる排他制御

lock ステートメントでは複数のスレッドが共通のロックオブジェクトを同時に占有することはできないが、セマフォを用いると指定された数のスレッドによる同時占有(エントリ)が可能となる。

System.Threading.Semaphore

標準のセマフォクラス。リソースに同時にアクセスできるスレッドの本数を制限できる。
using System; using System.Threading; public class Example { // Semaphore クラスインスタンス private static Semaphore _pool; : // メインメソッド public static void Main() { // セマフォの初期化 // 初期カウント : 0 // 最大カウント : 3 Console.WriteLine("メインスレッド開始、セマフォを初期化"); _pool = new Semaphore(initialCount: 0, maximumCount: 3); // 5 本のスレッドを起動 for(int i = 1; i <= 5; i++) { Thread t = new Thread(new ParameterizedThreadStart(Worker)); t.Start(i); } : // セマフォを 3 回解放 Console.WriteLine("メインスレッドがセマフォを解放"); _pool.Release(releaseCount: 3); Console.WriteLine("メインスレッド終了"); } // ワーカースレッド private static void Worker(object num) { // セマフォの空きを待機する Console.WriteLine($@"スレッド #{num} 開始、セマフォを待機"); _pool.WaitOne(); // セマフォに空きが発生したので、待ちを解除 Console.WriteLine($@"スレッド #{num} がセマフォを獲得"); : // セマフォを解放 Console.WriteLine($@"スレッド #{num} がセマフォを解放"); _pool.Release(); } }
出力結果 :
メインスレッド開始、セマフォを初期化 ( セマフォカウント : 0 / 3 ) スレッド #1 開始、セマフォを待機 ( セマフォカウント : 0 / 3 ) スレッド #2 開始、セマフォを待機 ( セマフォカウント : 0 / 3 ) スレッド #3 開始、セマフォを待機 ( セマフォカウント : 0 / 3 ) スレッド #4 開始、セマフォを待機 ( セマフォカウント : 0 / 3 ) スレッド #5 開始、セマフォを待機 ( セマフォカウント : 0 / 3 ) (セマフォが解放されるまでワーカースレッドは待機する) メインスレッドがセマフォを解放 ( セマフォカウント : 0 ⇒ 3 / 3 ) メインスレッド終了 スレッド #2 がセマフォを獲得 ( セマフォカウント : 3 ⇒ 2 / 3 ) スレッド #5 がセマフォを獲得 ( セマフォカウント : 2 ⇒ 1 / 3 ) スレッド #4 がセマフォを獲得 ( セマフォカウント : 1 ⇒ 0 / 3 ) (セマフォを獲得できなかったワーカースレッドはセマフォが解放されるまで待機する) スレッド #2 がセマフォを解放 ( セマフォカウント : 0 ⇒ 1 / 3 ) スレッド #3 がセマフォを獲得 ( セマフォカウント : 1 ⇒ 0 / 3 ) スレッド #5 がセマフォを解放 ( セマフォカウント : 0 ⇒ 1 / 3 ) スレッド #1 がセマフォを獲得 ( セマフォカウント : 1 ⇒ 0 / 3 ) スレッド #4 がセマフォを解放 ( セマフォカウント : 0 ⇒ 1 / 3 ) スレッド #3 がセマフォを解放 ( セマフォカウント : 1 ⇒ 2 / 3 ) スレッド #1 がセマフォを解放 ( セマフォカウント : 2 ⇒ 3 / 3 )
ここでは、セマフォカウントの最大が 3 のセマフォを用いる例を示している。
Main() メソッド内でセマフォを初期化、ワーカースレッドはセマフォを獲得( WaitOne() )しようとしているが、初期カウントが 0 (すべて獲得済み)のため待ちとなる。
Main() メソッド内でセマフォを解放( Release() )すると、ワーカースレッドがセマフォを獲得可能となる。このとき、引数に指定した回数だけセマフォから出る。
最大3本のスレッドが同時にエントリでき、セマフォを獲得できなかったワーカースレッドは引き続きセマフォの空きを待機する。
ここで、複数のスレッドがセマフォを待機している場合、獲得順は待ち順と一致しない(FIFOではない)。
また、lock ステートメントと同様本文中で await を使用できない。パフォーマンスは後述の SemaphoreSlim よりも不利である。

応用

Semaphore クラスは初期化時にセマフォの名称を指定できる。これにより、他のプロセスと共有される名前つきシステムセマフォを作成することができる
利用法のひとつとして、アプリケーションの多重起動抑止がある。リンク先を参照。

System.Threading.SemaphoreSlim

Semaphore をより軽量に改良したもの。使用法は同様。
システムセマフォを作成して他プロセスと共有することはできないが、同一プロセス内でのロックに用いるならば Semaphore よりもパフォーマンス面で有利。
また、Semaphore と異なり本文中で await を使用できる

ミューテックス( System.Threading.Mutex )による排他制御

ミューテックスの概念に関してはリンク先を参照のこと。
Semaphore と同様、名前つきにすることで他のプロセスと共有されるシステムミューテックスを作成できる

まとめ

下表におけるリソース消費量はあくまでも比較である。lock が最もリソース効率が良く、パフォーマンス面で有利である。
本文中に await を使用する場合は、SemaphoreSlim を、他プロセスとの共有(システムセマフォ)を行う場合は Semaphore を用いると良い。
Mutex は Semaphore よりもリソース効率が悪く、積極的に用いる利点は薄い。
(これは Mutex の実装が古く、.NET向けに最適化されていないことに起因する。)
比較の詳細は出典元を参照のこと。
lock SemaphoreSlim Semaphore Mutex
同時エントリ数 1 可変 可変 1
await の使用 不可 不可 不可
他プロセスと共有 不可 不可
リソース消費 最小 最大