モニターとロック


88

C#でスレッドセーフのためにMonitorクラスまたはlockキーワードのいずれかを使用するのが適切なのはいつですか?

編集: これまでの回答lockから、Monitorクラスへの一連の呼び出しの省略形であるように思われます。ロックコールの略記は正確には何ですか?またはより明確に、

class LockVsMonitor
{
    private readonly object LockObject = new object();
    public void DoThreadSafeSomethingWithLock(Action action)
    {
        lock (LockObject)
        {
            action.Invoke();
        }
    }
    public void DoThreadSafeSomethingWithMonitor(Action action)
    {
        // What goes here ?
    }
}

更新

ご協力いただきありがとうございます。ご提供いただいた情報の一部をフォローアップするために、別の質問を投稿しました。あなたはこの分野に精通しているようですので、私はリンクを投稿しました:ロックされた例外をロックして管理するこのソリューションの何が問題になっていますか?

回答:


89

エリックリペットは彼のブログでこれについて話します: ロックと例外は混ざりません

同等のコードは、C#4.0とそれ以前のバージョンでは異なります。


C#4.0では次のようになります。

bool lockWasTaken = false;
var temp = obj;
try
{
    Monitor.Enter(temp, ref lockWasTaken);
    { body }
}
finally
{
    if (lockWasTaken) Monitor.Exit(temp);
}

これはMonitor.Enter、ロックが取得されたときにフラグをアトミックに設定することに依存しています。


そして以前は:

var temp = obj;
Monitor.Enter(temp);
try
{
   body
}
finally
{
    Monitor.Exit(temp);
}

これは、Monitor.Enterとの間で例外がスローされないことに依存していますtry。デバッグコードでは、コンパイラがそれらの間にNOPを挿入し、それらの間でスレッドの中止を可能にしたため、この条件に違反したと思います。


私が述べたように、最初の例はC#4であり、もう1つは以前のバージョンで使用されているものです。
CodesInChaos 2011

補足として、CLRを介したC#は、lockキーワードの警告に言及しています。ロックを解放する前に、破損した状態(該当する場合)を復元するために何かをしたい場合があります。lockキーワードでは、catchブロックに物を入れることができないため、重要なルーチンに対しては、長いバージョンのtry-catch-finallyを作成することを検討する必要があります。
kizzx2 2011

5
共有状態を復元するIMOは、ロック/マルチスレッドと直交しています。したがって、lockブロック内でtry-catch / finallyを使用して実行する必要があります。
CodesInChaos 2011

2
@ kizzx2:このようなパターンは、リーダーライターロックで特に便利です。リーダーロックを保持するコード内で例外が発生した場合、保護されたリソースが破損する可能性があると予想する理由はないため、無効にする理由はありません。ライターロック内で例外が発生し、例外処理コードが保護されたオブジェクトの状態が修復されたことを明示的に示していない場合、オブジェクトが破損している可能性があるため、無効にする必要があります。私見ですが、予期しない例外によってプログラムがクラッシュすることはありませんが、破損している可能性のあるものはすべて無効になります。
スーパーキャット2013年

2
@ArsenZahrayPulse単純なロックは必要ありません。一部の高度なマルチスレッドシナリオでは重要です。Pulse直接使用したことはありません。
CodesInChaos 2014年

43

lock以下のためだけのショートカットですMonitor.Entertry+finallyMonitor.Exit。十分な場合は常にlockステートメントを使用します。TryEnterのようなものが必要な場合は、Monitorを使用する必要があります。


23

ロックステートメントは次と同等です。

Monitor.Enter(object);
try
{
   // Your code here...
}
finally
{
   Monitor.Exit(object);
}

ただし、MonitorはWait()およびPulse()も実行できることに注意してください。これらは、複雑なマルチスレッドの状況で役立つことがよくあります。

更新

ただし、C#4では実装が異なります。

bool lockWasTaken = false;
var temp = obj;
try 
{
     Monitor.Enter(temp, ref lockWasTaken); 
     //your code
}
finally 
{ 
     if (lockWasTaken) 
             Monitor.Exit(temp); 
} 

コメントとリンクについてはCodeInChaosに感謝します


C#4では、lockステートメントの実装が異なります。blogs.msdn.com/b/ericlippert/archive/2009/03/06/...
CodesInChaos

14

Monitorより柔軟です。モニターを使用しての私のお気に入りのユースケースは、あなたがしたくないときに待機するあなたのターンのためだけスキップ

//already executing? forget it, lets move on
if(Monitor.TryEnter(_lockObject))
{
    //do stuff;
    Monitor.Exit(_lockObject);
}

6

他の人が言っているように、lockは「同等」です

Monitor.Enter(object);
try
{
   // Your code here...
}
finally
{
   Monitor.Exit(object);
}

しかし、好奇心からlock、最初に渡した参照を保持し、変更してもスローされません。ロックされたオブジェクトを変更することはお勧めできません。変更したくない場合もあります。

しかし、繰り返しになりますが、科学の場合、これは問題なく機能します。

var lockObject = "";
var tasks = new List<Task>();
for (var i = 0; i < 10; i++)
    tasks.Add(Task.Run(() =>
    {
        Thread.Sleep(250);
        lock (lockObject)
        {
            lockObject += "x";
        }
    }));
Task.WaitAll(tasks.ToArray());

...そしてこれはしません:

var lockObject = "";
var tasks = new List<Task>();
for (var i = 0; i < 10; i++)
    tasks.Add(Task.Run(() =>
    {
        Thread.Sleep(250);
        Monitor.Enter(lockObject);
        try
        {
            lockObject += "x";
        }
        finally
        {
            Monitor.Exit(lockObject);
        }
    }));
Task.WaitAll(tasks.ToArray());

エラー:

タイプ 'System.Threading.SynchronizationLockException'の例外が70783sTUDIES.exeで発生しましたが、ユーザーコードで処理されませんでした

追加情報:オブジェクト同期メソッドが、同期されていないコードブロックから呼び出されました。

これは、が不変であるために変更さMonitor.Exit(lockObject);れたlockObjectものに作用しstrings、同期されていないコードブロックから呼び出しているためです。しかしとにかく。これはただの楽しい事実です。


「これは、Monitor.Exit(lockObject);がlockObjectに作用するためです」。次に、ロックはオブジェクトに対して何もしませんか?ロックはどのように機能しますか?
ユーゴアマリル

@YugoAmaryl、ロックステートメントは最初に渡された参照を念頭に置いてから、次のように変更された参照を使用する代わりにそれを使用するためだと思います。 object temp = lockObject; Monitor.Enter(temp); <...locked code...> Monitor.Exit(temp);
Zhuravlev A.

3

どちらも同じものです。ロックはcシャープキーワードであり、モニタークラスを使用します。

http://msdn.microsoft.com/en-us/library/ms173179(v=vs.80).aspx


3
msdn.microsoft.com/en-us/library/ms173179(v=vs.80).aspxを見てください。「実際、lockキーワードはMonitorクラスで実装されています。たとえば」
RobertoBr 2011

1
ロックの基本的な実装はモニターを使用しますが、それらは同じものではありません。ロック用に存在しないモニターによって提供されるメソッド、およびコードの別々のブロックでロックおよびロック解除できる方法を検討してください。
eran otzap

3

モニターのロックと基本的な動作(Enter + Exit)はほぼ同じですが、モニターには、より多くの同期の可能性を可能にするより多くのオプションがあります。

ロックはショートカットであり、基本的な使用法のオプションです。

より詳細な制御が必要な場合は、モニターの方が適しています。高度な使用法(バリア、セマフォなど)には、Wait、TryEnter、Pulseを使用できます。


1

ロック Lockキーワードは、1つのスレッドが一度に1つのコードを実行することを保証します。

lock(lockObject)

        {
        //   Body
        }

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

別のスレッドがロックされたコードを入力しようとすると、オブジェクトが解放されるまで待機、ブロックします。

モニター モニターは静的クラスであり、System.Threading名前空間に属しています。

オブジェクトに排他ロックを提供するため、任意の時点で1つのスレッドのみがクリティカルセクションに入ることができます。

C#でのモニターとロックの違い

ロックはMonitor.Enterのショートカットです。tryとfinallyで入力してください。ロックハンドルはtryを実行し、finallyを内部でブロックします Lock = Monitor + tryfinally。

あなたはより多くのコントロールを使用して高度なマルチスレッド・ソリューションを実装する場合TryEnter() Wait()Pulse()およびPulseAll()方法を、そしてモニタクラスは、あなたのオプションです。

C#Monitor.wait():スレッドは他のスレッドが通知するのを待ちます。

Monitor.pulse():スレッドが別のスレッドに通知します。

Monitor.pulseAll():スレッドは、プロセス内の他のすべてのスレッドに通知します


0

上記のすべての説明に加えて、lockはC#ステートメントですが、MonitorはSystem.Threading名前空間にある.NETのクラスです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.