C# Language
ロックステートメント
サーチ…
構文
- ロック(obj){}
備考
lock
ステートメントを使用すると、コードブロック内のコードへの異なるスレッドのアクセスを制御できます。競合状態を防ぐためによく使用されます。たとえば、複数のスレッドがコレクションから項目を読み込んだり、削除したりする場合などです。ロックがスレッドを強制的に他のスレッドがコードブロックを終了するのを待つようにすると、他の同期メソッドで解決できる遅延を引き起こす可能性があります。
MSDN
lockキーワードは、特定のオブジェクトに対する相互排他ロックを取得し、ステートメントを実行してロックを解放することによって、ステートメントブロックをクリティカルセクションとしてマークします。
lockキーワードは、別のスレッドがクリティカルセクションにある間に、あるスレッドがコードのクリティカルセクションに入らないようにします。別のスレッドがロックされたコードを入力しようとすると、オブジェクトが解放されるまでブロックされます。
ロックするプライベートオブジェクトを定義するか、 プライベート静的オブジェクト変数を定義してすべてのインスタンスに共通のデータを保護することをお勧めします。
C#5.0以降では、 lock
ステートメントは次のものと同等です。
bool lockTaken = false;
try
{
System.Threading.Monitor.Enter(refObject, ref lockTaken);
// code
}
finally
{
if (lockTaken)
System.Threading.Monitor.Exit(refObject);
}
C#4.0以前では、 lock
ステートメントは次のものと同等です。
System.Threading.Monitor.Enter(refObject);
try
{
// code
}
finally
{
System.Threading.Monitor.Exit(refObject);
}
簡単な使い方
lock
一般的な使用法は重要なセクションです。
次の例では、 ReserveRoom
は異なるスレッドから呼び出されることになっています。ここで競合状態を防ぐ最も簡単な方法は、 lock
との同期です。メソッド本体は2つ以上のスレッドが同時に実行できないようにlock
で囲まれていlock
。
public class Hotel
{
private readonly object _roomLock = new object();
public void ReserveRoom(int roomNumber)
{
// lock keyword ensures that only one thread executes critical section at once
// in this case, reserves a hotel room of given number
// preventing double bookings
lock (_roomLock)
{
// reserve room logic goes here
}
}
}
あるスレッドが別のスレッドを実行している間にそのスレッドがlock
ブロックに到達すると、前のスレッドは別のスレッドをブロックしてブロックを終了します。
ロックするプライベートオブジェクトを定義するか、プライベート静的オブジェクト変数を定義してすべてのインスタンスに共通のデータを保護することをお勧めします。
ロックステートメントでの例外のスロー
次のコードはロックを解除します。問題はありません。裏側のロックステートメントは、 try finally
として動作します
lock(locker)
{
throw new Exception();
}
詳細は、 C#5.0仕様書を参照してください。
フォームのlock
ステートメント
lock (x) ...
ここで、 x
は参照型の式であり、
bool __lockWasTaken = false;
try {
System.Threading.Monitor.Enter(x, ref __lockWasTaken);
...
}
finally {
if (__lockWasTaken) System.Threading.Monitor.Exit(x);
}
ただし、 x
は一度だけ評価されます。
ロック文での戻り値
次のコードはロックを解除します。
lock(locker)
{
return 5;
}
詳細な説明については、 このSOの回答が推奨されます。
ロックのためにObjectのインスタンスを使用する
C#の組み込みlock
ステートメントを使用する場合、ある種のインスタンスが必要ですが、その状態は関係ありません。 object
のインスタンスはこれに最適です:
public class ThreadSafe {
private static readonly object locker = new object();
public void SomeThreadSafeMethod() {
lock (locker) {
// Only one thread can be here at a time.
}
}
}
NB 。インスタンスType
(上記のコードでは、このために使用すべきではないtypeof(ThreadSafe)
のインスタンスからである) Type
アプリケーションドメイン間で共有されているので、ロックの範囲は、予想通り、それはいけないコードを含むことができる(例えば、場合ThreadSafe
にロードされます同じプロセス内に2つのAppDomainがあり、そのType
インスタンスでロックすると相互にロックされます)。
反パターンと落書き
スタックに割り当てられた/ローカル変数に対するロック
lock
を使用している間の誤りの1つは、関数内のロッカーとしてのローカルオブジェクトの使用です。これらのローカル・オブジェクト・インスタンスは関数の呼び出しごとに異なるため、 lock
は期待どおりに実行されません。
List<string> stringList = new List<string>();
public void AddToListNotThreadSafe(string something)
{
// DO NOT do this, as each call to this method
// will lock on a different instance of an Object.
// This provides no thread safety, it only degrades performance.
var localLock = new Object();
lock(localLock)
{
stringList.Add(something);
}
}
// Define object that can be used for thread safety in the AddToList method
readonly object classLock = new object();
public void AddToList(List<string> stringList, string something)
{
// USE THE classLock instance field to achieve a
// thread-safe lock before adding to stringList
lock(classLock)
{
stringList.Add(something);
}
}
同期が同期オブジェクト自体へのアクセスを制限すると仮定すると
あるスレッドが: lock(obj)
を呼び出し、別のスレッドがobj.ToString()
呼び出すと、2番目のスレッドはブロックされません。
object obj = new Object();
public void SomeMethod()
{
lock(obj)
{
//do dangerous stuff
}
}
//Meanwhile on other tread
public void SomeOtherMethod()
{
var objInString = obj.ToString(); //this does not block
}
サブクラスがいつロックするかを知ることを期待する
時には、ベースクラスは、そのサブクラスが特定の保護されたフィールドにアクセスするときにロックを使用するように設計されています。
public abstract class Base
{
protected readonly object padlock;
protected readonly List<string> list;
public Base()
{
this.padlock = new object();
this.list = new List<string>();
}
public abstract void Do();
}
public class Derived1 : Base
{
public override void Do()
{
lock (this.padlock)
{
this.list.Add("Derived1");
}
}
}
public class Derived2 : Base
{
public override void Do()
{
this.list.Add("Derived2"); // OOPS! I forgot to lock!
}
}
テンプレートメソッドを使用してロッキングをカプセル化する方がはるかに安全です。
public abstract class Base
{
private readonly object padlock; // This is now private
protected readonly List<string> list;
public Base()
{
this.padlock = new object();
this.list = new List<string>();
}
public void Do()
{
lock (this.padlock) {
this.DoInternal();
}
}
protected abstract void DoInternal();
}
public class Derived1 : Base
{
protected override void DoInternal()
{
this.list.Add("Derived1"); // Yay! No need to lock
}
}
ボックス化されたValueType変数のロックが同期しない
次の例では、プライベート変数は、モニタリソースがロックされることを期待して、関数へのobject
引数として提供されるため、暗黙的に囲まれていobject
。ボクシングは、IncInSync関数を呼び出す直前に発生します。したがって、ボックス化されたインスタンスは、関数が呼び出されるたびに別のヒープオブジェクトに対応します。
public int Count { get; private set; }
private readonly int counterLock = 1;
public void Inc()
{
IncInSync(counterLock);
}
private void IncInSync(object monitorResource)
{
lock (monitorResource)
{
Count++;
}
}
ボクシングはInc
関数で発生します:
BulemicCounter.Inc:
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldarg.0
IL_0003: ldfld UserQuery+BulemicCounter.counterLock
IL_0008: box System.Int32**
IL_000D: call UserQuery+BulemicCounter.IncInSync
IL_0012: nop
IL_0013: ret
これは、ボックス化されたValueTypeをモニタのロックに使用できないことを意味するものではありません。
private readonly object counterLock = 1;
今ボクシングはコンストラクタで発生しますが、これはロックに適しています:
IL_0001: ldc.i4.1
IL_0002: box System.Int32
IL_0007: stfld UserQuery+BulemicCounter.counterLock
より安全な代替手段が存在する場合にロックを不必要に使用する
非常に一般的なパターンは、プライベートList
またはDictionary
をスレッドセーフなクラスで使用し、アクセスするたびにロックすることです。
public class Cache
{
private readonly object padlock;
private readonly Dictionary<string, object> values;
public WordStats()
{
this.padlock = new object();
this.values = new Dictionary<string, object>();
}
public void Add(string key, object value)
{
lock (this.padlock)
{
this.values.Add(key, value);
}
}
/* rest of class omitted */
}
values
辞書にアクセスするメソッドが複数ある場合は、コードが非常に長くなる可能性があります。さらに重要なことは、すべての時間をロックすることでその意図が覆い隠されることです。ロックも非常に忘れやすいため、適切なロックがないとバグを見つけにくくなる可能性があります。
ConcurrentDictionary
を使用することで、完全にロックされないようにすることができます。
public class Cache
{
private readonly ConcurrentDictionary<string, object> values;
public WordStats()
{
this.values = new ConcurrentDictionary<string, object>();
}
public void Add(string key, object value)
{
this.values.Add(key, value);
}
/* rest of class omitted */
}
同時収集を使用すると、 すべてがロックフリー技術をある程度採用しているため、パフォーマンスが向上します。