【问题标题】:Calling Thread.Sleep() inside lock statement in .net在 .net 中的 lock 语句中调用 Thread.Sleep()
【发布时间】:2012-02-26 15:49:27
【问题描述】:

我想知道在已经获得 Monitor 的线程上调用 Threa.Sleep 是否会在进入睡眠状态之前释放锁:

object o = new object();
Montior.Enter(o);
Thread.Sleep(1000);
Monitor.Exit(o);

当线程被挂起时——其他线程可以获取o吗?

【问题讨论】:

  • 在锁内调用 Thread.Sleep 是导致应用程序无响应的秘诀。您应该尽可能在锁内执行最少的工作,否则等待资源的其他线程会堆积起来等待获取锁。
  • @JaredShaver 除了在一大堆情况下,例如锁定将数据发送到从不同线程负载获取数据但如果发送数据太快会崩溃的硬件。

标签: c# .net synchronization locking


【解决方案1】:

不,线程不会在挂起/休眠之前释放lock

在休眠线程唤醒并释放锁定对象之前,没有其他线程能够获取o

【讨论】:

    【解决方案2】:

    不,在EnterExit 之间,无论你在这之间做什么,没有其他线程可以获取锁。

    【讨论】:

      【解决方案3】:

      不,如果你休眠,锁不会被释放。

      如果您想要发布它,请使用Monitor.Wait(o, timeout);此外,您还可以使用它从另一个线程发出信号 - 另一个线程可以使用Monitor.Pulse[All](同时持有锁)在“超时”之前唤醒等待线程(它也会重新获取进程中的锁) .

      请注意,无论何时使用 Enter/Exit,您都应该考虑使用 try/finally - 否则如果发生异常,您将面临不释放锁的风险。

      例子:

      bool haveLock = false;
      try {
          Monitor.Enter(ref haveLock);
           // important: Wait releases, waits, and re-acquires the lock
          bool wokeEarly = Monitor.Wait(o, timeout);
          if(wokeEarly) {...}
      } finally {
          if(haveLock) Monitor.Exit(o);
      }
      

      另一个线程可以做:

      lock(o) { Monitor.PulseAll(o); }
      

      这将推动当前在该对象上等待的任何线程(但如果没有对象正在唤醒,则不执行任何操作)。强调:等待线程仍然需要等待脉冲线程释放锁,因为它需要重新获取。

      【讨论】:

      • 但是 Pulse 方法会自动(并且原子地...)释放对象的锁,对吧
      • @PiniSalim no, Pluse 明确释放锁 - 另一个(等待)线程被移动到 ready 队列,并且获得锁并仅在锁被Monitor.Exit 释放时继续 - 即当它离开“另一个线程可以做:”示例的lock(o) {...} 部分时。
      • @PiniSalim 或来自 MSDN,强调我的:“接收到脉冲后,等待线程被移动到就绪队列。当调用 Pulse 释放锁的线程时,就绪队列中的下一个线程(不一定是被脉冲的线程)获取锁。”
      【解决方案4】:

      根据我的经验,在锁定块中间调用 Thread.Sleep 会导致锁定线程失去锁定(即上下文切换)。 我运行了以下程序:

       using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      using System.Threading;
      
      class Program
      {
          static void Main(string[] args)
          {
              Class1 c1 = new Class1();
              Class2 c2 = new Class2();
      
              Thread t1 = new Thread(c1.DoSomthing);
              Thread t2 = new Thread(c2.DoSomthing);
              t1.Start();
              Thread.Sleep(500);
              t2.Start();
          }
      }
      
      class Class1
      {
          object m_objSyncLock = new object();
          ManualResetEvent m_objSleep = new ManualResetEvent(true);
      
          public void DoSomthing()
          {
              Monitor.Enter(m_objSyncLock);
                  int i = 1; //break point here
                   Thread.Sleep(565);
                  i++; //break point here
              Monitor.Exit(m_objSyncLock);
              }
          }
      }
      
      class Class2
      {
          object m_objSyncLock = new object();
      
          public void DoSomthing()
          {
              lock (m_objSyncLock)
              {
                  int i = 1;    //break point here           
                  i++;
              }
          }
      }
      

      在第 30、32、46 行添加断点,注意第 32 行出现在第 1 行,然后是第 48 行,最后是第 34 行。 这不是说 Thread.Sleep 调用让我失去了锁吗?

      此外,当使用 ManualResetEvent.WaitOne 而不是 Thread.Sleep 时,执行线程并没有失去排他性(除了切换到 ManualResetEvent 本身)。

      我不是专家,但这个简单的测试表明 Thread.Sleep 可能会让您在使用 ManualResetEvent.WaitOne 时使锁丢失。

      【讨论】:

      • 我无法正确理解您的观点,因为它很难找到行号。如果您可以在您引用的每一行上添加注释,说明调试器会发生什么,这将有助于弄清楚您的观点。
      • 啊,现在我知道你错在哪里了。您已创建 2 个用于锁定的对象。那不会做任何事情。 (如果您在 2 个地方有“新”,则表示 2 个对象)。如果你想正确锁定,你应该锁定同一个对象。
      • @HimeshSameera 确实!
      猜你喜欢
      • 1970-01-01
      • 2011-02-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多