同时获取多个线程同步锁

时间:2016-05-30 07:57:32

标签: c# multithreading locking c++-cli mutex

让我们假设以下代码,在我的应用程序中以类似的方式使用:

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

这些函数可能随时被不同的线程调用。

可能会发生这种情况 (超时为500毫秒)
@t = 0ms,线程B正在执行UseResource2(),需要400ms,
@t = 100ms,线程Z正在调用UseAllResources(),对oResource1进行锁定并且必须等待oResource2上的锁定,
@t = 200ms,线程A正在调用UseResource1()并且必须等待oResource1上的锁定,该线程由线程Z执行,
@t = 400ms,线程B完成,线程Z锁定oResource2并开始工作,将需要400ms,
@t = 700ms,线程A超时,虽然它只需要50ms并且可以在线程Z仍在等待时工作。

我更希望线程Z失败,如果有的话,因为超时应该是所有锁的整体值。

我可以同时开始获取多个锁吗?

2 个答案:

答案 0 :(得分:0)

解决方案可能是使用ReaderWriterLockSlim类。以下代码在构造函数中包装了一个函数(您要完成的工作)。或者,您可以将该功能移至DoWork方法,以更改您访问资源的方式。

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设置为1
  资源2设置为2
  T1释放资源
  锁定线程T3上的超时时间   T3释放资源
  T3释放资源
  T2释放资源
  资源1是1
  资源2是2

答案 1 :(得分:0)

编辑:
TL;博士
有用。开箱即用的解决方案就在这个答案的最后 / EDIT

在我的问题中添加时间样本时,我有了解决方案的想法:

我的目标是仅在所有锁都空闲时获取锁(在下面实现),但是可以很容易地将其更改为保持锁并且仅在超时时返回接收到的锁。

这部分可以进一步移动到一个静态辅助函数,它接收一组对象(或互斥体)来锁定和超时。
编辑:
完成后,请参阅答案的结尾。

//-------------------------------------
// 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()开始锁定
01.903 UseResource2()完成工作
01.903 UseAllResources()开始工作
02.211 UseResource1()监视器超时
02.303 UseAllResources()完成工作

输出UseAllResources2():(第一个解决方案,直接实施)
42.002 UseResource2()开始锁定
42.002 UseResource2()开始工作
42.103 UseAllResources2()开始锁定1和2
42.206 UseResource1()开始锁定
42.206 UseResource1()开始工作
42.256 UseResource1()完成工作
42.402 UseResource2()完成工作
42.427 UseAllResources2()开始工作
42.827 UseAllResources2()完成工作

输出UseAllResources3(keepLo​​cks = false):(第二个解决方案,开箱即用) 16.392 UseResource2()开始锁定
16.393 UseResource2()开始工作
16.494 UseAllResources3()开始锁定1和2
16.595 UseResource1()开始锁定
16.597 UseResource1()开始工作
16.647 UseResource1()完成工作
16.793 UseResource2()完成工作
16.818 UseAllResources3()开始工作
17.218 UseAllResources3()完成工作
//与之前相同,正如所料。

输出UseAllResources3(keepLo​​cks = true):(第二个解决方案,开箱即用) 31.965 UseResource2()开始锁定
31.965 UseResource2()开始工作
32.068 UseAllResources3()开始锁定1和2
32.169 UseResource1()开始锁定
32.365 UseResource2()完成工作
32.390 UseAllResources3()开始工作
32.672 UseResource1()监视器超时
32.790 UseAllResources3()完成工作
//正如预期的那样在线程A上超时

务! : - )

<强> TL;博士
这是开箱即用的解决方案:

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