ロックステートメント

# 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 の一般的な使い方 はクリティカル セクションです。

次の例では ReserveRoom 異なるスレッドから呼び出されることになっています。 lock との同期 ここで競合状態を防ぐ最も簡単な方法です。メソッド本体は lock で囲みます これにより、2 つ以上のスレッドが同時に実行できないようになります。

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 に達した場合 -ed ブロック内で別のスレッドが実行されている場合、前者は別のスレッドがブロックを終了するのを待ちます。

ベスト プラクティスは、プライベート オブジェクトを定義してロックするか、privatestatic オブジェクト変数を定義してすべてのインスタンスに共通のデータを保護することです。

# ロック文でリターン

次のコードはロックを解除します。

lock(locker)
{
    return 5;
}

詳細な説明については、この SO 回答をお勧めします。

# アンチパターンと落とし穴

# スタック割り当て/ローカル変数のロック

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);
    }
}

# ロックが同期オブジェクト自体へのアクセスを制限していると仮定

1 つのスレッドが呼び出す場合: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 として提供されるため、暗黙的にボックス化されます。 ボックス化は、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 */
}

並行コレクションを使用すると、パフォーマンスも向上します。これは、すべてのコレクションがロックフリー技術をある程度採用しているためです。

# オブジェクトのインスタンスをロックに使用する

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.
    }
  }
}

注意 . Type のインスタンス これには使用しないでください (上のコードでは typeof(ThreadSafe) ) Type のインスタンスのため AppDomains 間で共有されているため、ロックの範囲に含まれてはならないコードが含まれる可能性があります (例:if ThreadSafe 同じプロセスで 2 つの AppDomains にロードされ、その Type でロックされます インスタンスは相互にロックします)。

# 構文

  • lock (obj) {}

# コメント

lock の使用 ステートメントを使用すると、コード ブロック内のコードへのさまざまなスレッドのアクセスを制御できます。複数のスレッドがコレクションからアイテムを読み取ったり削除したりするなど、競合状態を防ぐためによく使用されます。ロックは、他のスレッドがコード ブロックを終了するまでスレッドを待機させるため、他の同期方法で解決できる遅延が発生する可能性があります。

MSDN

lock キーワードは、特定のオブジェクトの相互排除ロックを取得し、astatement を実行してからロックを解放することにより、ステートメント ブロックをクリティカル セクションとしてマークします。

lock キーワードは、別のスレッドがクリティカル セクションにある間に、あるスレッドがコードのクリティカル セクションに入らないようにします。別のスレッドがロックされたコードを入力しようとすると、オブジェクトが解放されるまでブロックされます。

ベスト プラクティスは、ロックする **private** オブジェクト、または privatestatic を定義することです すべてのインスタンスに共通のデータを保護するオブジェクト変数

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);
}