2016-05-30 12 views
4

のは、私のアプリケーションで同様の方法で使用される次のコードを、と仮定しよう:取得複数のスレッド同期ロックを同時に

//------------------------------------- 
void UseAllResources() 
{ 
    bool bSuccess1 = false; 
    bool bSuccess2 = false; 
    try 
    { 
    bSuccess1 = Monitor::TryEnter (oResource1, msc_iTimeoutMonitor); 
    if (!bSuccess1) return; 
    bSuccess2 = Monitor::TryEnter (oResource2, msc_iTimeoutMonitor); 
    if (!bSuccess2) return; 

    // work on oResource1 and oResource2 
    } finally { 
    if (bSuccess2) 
     Monitor::Exit (oResource2); 
    if (bSuccess1) 
     Monitor::Exit (oResource1); 
    } 
} 

//------------------------------------- 
void UseResource1() 
{ 
    bool bSuccess = false; 
    try { 
    bSuccess = Monitor::TryEnter (oResource1, msc_iTimeoutMonitor); 
    if (!bSuccess) return; 

    // work on oResource1 
    } finally { 
    if (bSuccess) Monitor::Exit (oResource1); 
    } 
} 

//------------------------------------- 
void UseResource2() 
{ 
    same like UseResource1(), but using oResource2 
} 

これらの機能は、異なるスレッドによっていつでも呼び出すことがあります。


ことがある(タイムアウトは500msの)
@ T = 0msと、スレッドBは400msの、
は @ T = 100ミリ秒、スレッドZ)が(UseAllResourcesを呼び出して、ロックを取るを取る、UseResource2()実行されていますスレッドAがUseResource1()を呼び出していて、スレッドZによって取られたoResource1のロックを待たなければならない場合、
@ t = 200ms @スレッドBが完了すると、スレッドZはoResource2のロックを取得して作業を開始し、400msかかります。
@ t = 700ms、スレッドAタイムアウト、その他スレッドZがまだ待っている間に50msしか必要とせずに作業できました。

タイムアウトはすべてのロックの全体的な値でなければならないので、スレッドZはむしろ失敗するようにします。

複数のロックの取得を同時に開始できますか?

+2

買収...悪い、IMO)。しかし、複数の取得されたロックは頭痛や白いデバッグの夜(良いデッドロックのため)の良い出発点です、あなたは一度に1つのリソースしか取得して使用できませんか?あなたは_long syntax_の代わりに 'msclr :: lock'を使いたいかもしれません。もしリソースが関連していれば、2つのファインドロックで遊ぶよりも粗いロックを使うほうがいいです**(そして、 'ReaderWriterLockSlim'のパフォーマンスを調べる) –

+0

@AdrianoRepetti:データをシフトするので、いくつかの機能では、あるリソースから別のリソースへタイムアウトを使用することでデッドロックが発生しないようにする必要があります。 –

+0

trueですが、ロックを取得できない場合は、必要な操作を実行せずに戻ります。それは実際にプロダクションで何をしたいのですか?タイムアウトがうまくいかないので、あなたはこの質問をしています。ポイントは...同時ロックを取得せず、粗い(共有)ロックに移動しないことです。ロック戦略は(IMO)複雑すぎて架空の例では設計できませんが、あなたの読み込みパターンは何ですか?パターンを書く?並行読み取りと書き込み?より粗いロックを使用すると、パフォーマンスは低下しますか?細かいロックのメリットは何ですか?あなたがロック取得ロックを導入する必要があるのであれば、BTW ... –

答えて

0

EDITです:
TL。ドクター
それは動作します。すぐに使える解決策はこの答えの最後です。
/EDIT

私の質問にタイミングサンプルを追加している間、私は解決策のためのアイデアを持っていた:

私の目標は、それらのすべてが(以下実装)自由である場合にのみ、ロックを取得することで、ロックを保持し、タイムアウト時に受信したロックのみを返すように簡単に変更できます。

この部分は、ロックするオブジェクト(またはmutex)の配列とタイムアウトを受け取る静的ヘルパー関数にさらに移動できます。
編集:
完了、答えの最後を参照してください。

//------------------------------------- 
// using direct implementation 
//------------------------------------- 
void UseAllResources2() 
{ 
    bool bSuccess1 = false; 
    bool bSuccess2 = false; 
    try 
    { 
    Console::WriteLine (DateTime::Now.ToString("ss.fff") + " "__FUNCTION__"() start locking 1 and 2"); 
    DateTime tStart = DateTime::Now; 
    bool bSuccess = false; 
    do 
    { 
     bSuccess1 = Monitor::TryEnter (oResource1); 
     bSuccess2 = Monitor::TryEnter (oResource2); 
     bSuccess = bSuccess1 && bSuccess2; 
     if (!bSuccess) 
     { 
     if (bSuccess1) Monitor::Exit (oResource1); 
     if (bSuccess2) Monitor::Exit (oResource2); 
     Thread::Sleep(10); 
     } 
    } 
    while (!bSuccess && (DateTime::Now - tStart).TotalMilliseconds < msc_iTimeoutMonitor); 
    if (!bSuccess) 
    { 
     Console::WriteLine (DateTime::Now.ToString("ss.fff") + " "__FUNCTION__"() monitor timeout"); 
     return; 
    } 

    Console::WriteLine (DateTime::Now.ToString("ss.fff") + " "__FUNCTION__"() start work"); 
    Thread::Sleep (400); 
    Console::WriteLine (DateTime::Now.ToString("ss.fff") + " "__FUNCTION__"() finish work"); 
    } finally { 
    if (bSuccess2) 
     Monitor::Exit (oResource2); 
    if (bSuccess1) 
     Monitor::Exit (oResource1); 
    } 
} 

//------------------------------------- 
// using Out-Of-Box solution 
//------------------------------------- 
static void UseAllResources3() 
{ 
    bool bSuccess = false; 
    try 
    { 
    Console::WriteLine (DateTime::Now.ToString("ss.fff") + " "__FUNCTION__"() start locking 1 and 2"); 
    bSuccess = MonitorTryEnter (gcnew array<Object^>{oResource1, oResource2}, 500, 10, false); 
    if (!bSuccess) 
    { 
     Console::WriteLine (DateTime::Now.ToString("ss.fff") + " "__FUNCTION__"() monitor timeout"); 
     return; 
    } 

    Console::WriteLine (DateTime::Now.ToString("ss.fff") + " "__FUNCTION__"() start work"); 
    Thread::Sleep (400); 
    Console::WriteLine (DateTime::Now.ToString("ss.fff") + " "__FUNCTION__"() finish work"); 
    } finally { 
    if (bSuccess) 
    { 
     Monitor::Exit (oResource2); 
     Monitor::Exit (oResource1); 
    } 
    } 
} 

私のテストのための()メイン:

int main() 
{ 
// first run is for the CLR to load everything 
    Thread^ oThreadA = gcnew Thread (gcnew ThreadStart (&UseResource1)); 
    Thread^ oThreadB = gcnew Thread (gcnew ThreadStart (&UseResource2)); 
// Thread^ oThreadZ = gcnew Thread (gcnew ThreadStart (&UseAllResources)); 
// Thread^ oThreadZ = gcnew Thread (gcnew ThreadStart (&UseAllResources2)); 
    Thread^ oThreadZ = gcnew Thread (gcnew ThreadStart (&UseAllResources3)); 

    oThreadB->Start(); 
    Thread::Sleep(100); 
    oThreadZ->Start(); 
    Thread::Sleep(100); 
    oThreadA->Start(); 

    Thread::Sleep (2000); 
    Console::WriteLine(); 

// now that all code is JIT compiled, the timestamps are correct. 
// Logs below are from this 2nd run. 
    oThreadA = gcnew Thread (gcnew ThreadStart (&UseResource1)); 
    oThreadB = gcnew Thread (gcnew ThreadStart (&UseResource2)); 
// oThreadZ = gcnew Thread (gcnew ThreadStart (&UseAllResources)); 
// oThreadZ = gcnew Thread (gcnew ThreadStart (&UseAllResources2)); 
    oThreadZ = gcnew Thread (gcnew ThreadStart (&UseAllResources3)); 

    oThreadB->Start(); 
    Thread::Sleep(100); 
    oThreadZ->Start(); 
    Thread::Sleep(100); 
    oThreadA->Start(); 

    Thread::Sleep (2000); 
} 

出力UseAllResources():開始)((質問から)
01.503 UseResource2()開始
01.503 UseResource2をロックする作業
01.604 UseAllResources()ロック開始1
01.604 UseAllResources()ロック開始2
01.707 UseResource1())仕上げ作業

出力UseAllResources2()(タイムアウト
02.303 UseAllResourcesを監視する)作業
02.211 UseResource1(開始)(
01.903 UseResource2()仕上げ作業
01.903 UseAllResourcesロック開始:
(第1溶液、直接実装)
42.002 UseResource2は())(ワーク
42.103 UseAllResources2開始)(
42.002 UseResource2ロック起動1及び2ロック開始します 42.206 UseResource1()作業開始)(
42.206 UseResource1ロック開始
42.256 UseResource1()仕上げ作業
42.402 UseResource2()仕上げ作業
42.427 UseAllResources2()開始ワーク
42.827 UseAllResources2()仕上げ作業

出力UseAllResources3(keepLo​​cks =偽):(2溶液、アウトオブボックス実装)
16.392 UseResource2()
16.393 UseResouロック開始rce2()は作業開始)(
16.597 UseResource1ロック開始)()(ワーク
16.494 UseAllResources3起動1及び2
16.595 UseResource1ロック開始
16.647 UseResource1()仕上げ作業
16.793 UseResource2()仕上げ作業
16.818 UseAllResources3()start work
17.218 UseAllResources3()は作業を完了します
//これは以前と同じです。

出力UseAllResources3(keepLo​​cks =真):(2溶液、アウトオブボックス実装)
31.965 UseResource2()
31.965 UseResource2を(ロック開始)
32.068作業を開始UseAllResources3(1)をロック開始および2
32.169 UseResource1()ロックの開始
32。スレッドAに365 UseResource2()仕上げ作業
32.390 UseAllResources3()開始ワーク
32.672 UseResource1()監視タイムアウト
32.790 UseAllResources3()仕上げ作業
//タイムアウト、予想通り。

WORKS! :-)

TL; DR
はここでアウトオブボックスソリューションです:あなたがロックを同期させるための別の外側のロックを導入しない限り、あなたは(すべて一緒に複数のロックを取得することはできません

//---------------------------------------------------------------------------- 
// MonitorTryEnter 
//---------------------------------------------------------------------------- 
bool MonitorTryEnter (array<Object^>^ i_aoObject, int i_iTimeout, int i_iSleep, bool i_bKeepLocks) 
{ 
    if (!i_aoObject) 
    return false; 
    if (i_iSleep < 0) 
    i_iSleep = 10; 

    List<Object^>^ listObject = gcnew List<Object^>; 
    for (int ixCnt = 0; ixCnt < i_aoObject->Length; ixCnt++) 
    if (i_aoObject[ixCnt]) 
     listObject->Add (i_aoObject[ixCnt]); 
    if (listObject->Count <= 0) 
    return false; 
    array<bool>^ abSuccess = gcnew array<bool>(listObject->Count); 

    DateTime tStart = DateTime::Now; 
    bool bSuccess = true; 
    do 
    { 
    bSuccess = true; 
    if (!i_bKeepLocks) 
     abSuccess = gcnew array<bool>(listObject->Count); 

    for (int ixCnt = 0; ixCnt < listObject->Count; ixCnt++) 
    { 
     if (!abSuccess[ixCnt]) 
     abSuccess[ixCnt] = Monitor::TryEnter (listObject[ixCnt]); 
     bSuccess = bSuccess && abSuccess[ixCnt]; 
     if (!bSuccess) 
     break; 
    } 

    if (!bSuccess) 
    { 
     if (!i_bKeepLocks) 
     { 
     for (int ixCnt = 0; ixCnt < listObject->Count; ixCnt++) 
     { 
      if (abSuccess[ixCnt]) 
      { 
      Monitor::Exit (listObject[ixCnt]); 
      abSuccess[ixCnt] = false; 
      } 
     } 
     } 
     Thread::Sleep(i_iSleep); 
    } 
    } 
    while (!bSuccess && (DateTime::Now - tStart).TotalMilliseconds < i_iTimeout); 

    if (!bSuccess) 
    { 
    for (int ixCnt = 0; ixCnt < listObject->Count; ixCnt++) 
     if (abSuccess[ixCnt]) 
     Monitor::Exit (listObject[ixCnt]); 
    } 

    return bSuccess; 
} 
0

解決策は、ReaderWriterLockSlimクラスを使用することです。次のコードは、コンストラクタ内の関数(実行する作業)をラップします。あるいは、DoWorkメソッドに関数を移動して、リソースへのアクセス方法を変更することもできます。 1つの
リソース2セットに

LockedResourceのimplmentation

class LockedResource 
{ 
    public delegate void RefAction(); 

    ReaderWriterLockSlim resourceLock; 

    public LockedResource() 
    { 
     //Warning: SupportsRecursion is risky, you should remove support for recursive whenever possible 
     resourceLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); 
    } 

    public bool DoWork(RefAction work, string threadname, int timeout = -1) 
    { 
     try 
     { 
      if (resourceLock.TryEnterWriteLock(timeout)) 
      { 
       if (work != null) 
       { 
        work(); 
       } 
      } 
      else 
      { 
       Console.WriteLine("Lock time out on thread {0}", threadname); 
      } 
     } 
     finally 
     { 

      Console.WriteLine("{0} releasing resource", threadname); 
      if(resourceLock.IsWriteLockHeld) 
      { 
       resourceLock.ExitWriteLock(); 
      } 
     } 

     return false; 
    } 
} 

サンプル使用

static void Main(string[] args) 
{ 
     object oResouce1 = "-"; 
     object oResouce2 = "-"; 

     LockedResource lock1 = new LockedResource(); 
     LockedResource lock2 = new LockedResource(); 

     //the event wait handles is not required, only used to block thread so that resource values can be printed out at the end of the program 
     var h1 = new EventWaitHandle(false, EventResetMode.ManualReset); 
     var h2 = new EventWaitHandle(false, EventResetMode.ManualReset); 
     var h3 = new EventWaitHandle(false, EventResetMode.ManualReset); 

     WaitHandle[] waitHandles = { h1, h2, h3 }; 

     var t1 = new Thread(() => 
     { 
      lock1.DoWork(() => 
      { 
       oResouce1 = "1"; 
       Console.WriteLine("Resource 1 set to 1"); 
      },"T1"); 

      h1.Set(); 
     }); 

     var t2 = new Thread(() => 
     { 
      lock2.DoWork(() => 
      { 
       oResouce2 = "2"; 
       Console.WriteLine("Resource 2 set to 2"); 
       Thread.Sleep(10000); 

      }, "T2"); 
      h2.Set(); 
     }); 

     var t3 = new Thread(() => 
     { 
      lock1.DoWork(() => 
      { 
       lock2.DoWork(() => 
       { 
        oResouce1 = "3"; 
        Console.WriteLine("Resource 1 set to 3"); 

        oResouce2 = "3"; 
        Console.WriteLine("Resource 2 set to 3"); 
       }, "T3", 1000); 
       h3.Set(); 

      }, "T3"); 

     }); 
     t1.Start(); 
     t2.Start(); 
     t3.Start(); 


     WaitHandle.WaitAll(waitHandles); 
     Console.WriteLine("Resource 1 is {0}", oResouce1); 
     Console.WriteLine("Resource 2 is {0}", oResouce2); 

     Console.ReadLine(); 
} 

出力

リソース1組2
T1リソース解放に
T2リソース解放
リソース1スレッドT3上 ロックタイムアウト
T3リソース解放
T3リソース解放は1
リソース2が2

+0

私が正しく理解していれば、 't3'は同時にリソース1とリソース2を取っていませんが、お互いに後になります。これは私の意図ではありません。私は2つの異なる資源を同時に使う仕事をしたい。ここでは、各リソースを単一のロックにラップし、連続して動作するように見えます。 –

+0

@jeff LockRecursionPolicy.SupportsRecursionはcum grano salis(IMO)と併用する必要がありますが、リエントラントコードは本質的に危険です。 ReaderWriterLockSlimはOPの使用パターンに依存しますが、ダブルロックの問題を緩和しません(再度依存します)。 –

+0

@AdrianoRepettiヘッドアップのおかげで、私はこの問題を認識していますが、私は同意し、私の例に警告が来ているはずです。ありがとう。 –

関連する問題