【问题标题】:Monitor vs lock监控与锁定
【发布时间】:2011-06-26 02:49:03
【问题描述】:

什么时候适合在 C# 中使用 Monitor 类或 lock 关键字来保证线程安全?

编辑: 从到目前为止的答案看来,lock 是对Monitor 类的一系列调用的简写。 lock call 的简写到底是什么?或者更明确地说,

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

更新

感谢大家的帮助:我已经发布了另一个问题,作为对你们提供的一些信息的跟进。由于您似乎精通这方面的知识,因此我发布了链接:What is wrong with this solution to locking and managing locked exceptions?

【问题讨论】:

    标签: c# .net multithreading locking monitor


    【解决方案1】:

    Eric Lippert 在他的博客中谈到了这一点: Locks and exceptions do not mix

    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.Entertry 之间没有抛出异常。我认为在调试代码中违反了这个条件,因为编译器在它们之间插入了一个 NOP,从而使它们之间的线程中止成为可能。

    【讨论】:

    • 正如我所说,第一个例子是 C#4,另一个是早期版本使用的。
    • 作为旁注,通过 CLR 的 C# 提到了 lock 关键字的警告:您可能经常想要在释放锁之前执行一些操作来恢复损坏状态(如果适用)。由于 lock 关键字不允许我们将东西放在 catch 块中,我们应该考虑为非平凡的例程编写长版本的 try-catch-finally。
    • IMO 恢复共享状态与锁定/多线程正交。所以应该在 lock 块内使用 try-catch/finally 来完成。
    • @kizzx2:这种模式特别适合读写器锁。如果在持有读取器锁的代码中发生异常,则没有理由预期受保护的资源可能会损坏,因此也没有理由使其无效。如果在写入器锁中发生异常,并且异常处理代码没有明确表示受保护对象的状态已被修复,则表明该对象可能已损坏并且应该失效。恕我直言,意外异常不应使程序崩溃,而应使任何可能损坏的内容无效。
    • @ArsenZahray 你不需要Pulse 来进行简单的锁定。这在一些高级多线程场景中很重要。我从来没有直接使用过Pulse
    【解决方案2】:

    锁定 Lock 关键字确保一个线程一次执行一段代码。

    锁定(锁定对象)

            {
            //   Body
            }
    

    lock 关键字通过获取给定对象的互斥锁,执行语句然后释放锁来将语句块标记为临界区

    如果另一个线程试图输入一个锁定的代码,它会等待,阻塞,直到对象被释放。

    监控 Monitor 是一个静态类,属于 System.Threading 命名空间。

    它为对象提供排他锁,以便在任何给定时间点只有一个线程可以进入临界区。

    C#中Monitor和lock的区别

    锁是 Monitor.Enter 的快捷方式,使用 try 和 finally。 锁定句柄尝试并最终在内部阻塞 Lock = Monitor + try finally.

    如果您希望使用TryEnter()Wait()Pulse()PulseAll() 方法实现高级多线程解决方案的更多控制权,那么 Monitor 类是您的选择。

    C#Monitor.wait():一个线程等待其他线程通知。

    Monitor.pulse():一个线程通知另一个线程。

    Monitor.pulseAll():一个线程通知一个进程中的所有其他线程

    【讨论】:

      【解决方案3】:

      除了以上所有解释之外,lock 是一个 C# 语句,而 Monitor 是一个位于 System.Threading 命名空间中的 .NET 类。

      【讨论】:

        【解决方案4】:

        Monitor 更灵活。我最喜欢使用监视器的用例是当你不想等待轮到你而只是跳过

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

        【讨论】:

          【解决方案5】:

          正如其他人所说,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 ,我想这是因为 lock 语句记住首先传递引用然后使用它而不是使用更改的引用,例如:object temp = lockObject; Monitor.Enter(temp); &lt;...locked code...&gt; Monitor.Exit(temp);
          【解决方案6】:

          lock 只是 Monitor.Enter 的快捷方式,try + finallyMonitor.Exit。只要够用就使用 lock 语句 - 如果您需要 TryEnter 之类的东西,则必须使用 Monitor。

          【讨论】:

            【解决方案7】:

            监视器的锁定和基本行为(进入+退出)或多或少相同,但监视器有更多选项,可以让您有更多同步可能性。

            锁是一种快捷方式,是基本使用的选项。

            如果您需要更多控制,显示器是更好的选择。您可以将 Wait、TryEnter 和 Pulse 用于高级用途(如屏障、信号量等)。

            【讨论】:

              【解决方案8】:

              一个lock语句相当于:

              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 的 cmets 和 links

              【讨论】:

              【解决方案9】:

              两者都是一样的。 lock 是 c sharp 关键字并使用 Monitor 类。

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

              【讨论】:

              • msdn.microsoft.com/en-us/library/ms173179(v=vs.80).aspx"其实lock关键字是用Monitor类实现的,比如"
              • 锁的底层实现使用了Monitor,但它们不是一回事,考虑一下monitor提供的锁不存在的方法,以及您可以在单独的代码块中锁定和解锁的方式。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2016-02-21
              • 2012-10-28
              • 1970-01-01
              • 2020-04-04
              • 2014-10-09
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多