监控与锁定


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

更新资料

谢谢大家的帮助:作为您提供的某些信息的后续,我还发布了另一个问题。由于您似乎对此领域很精通,因此我发布了链接:锁定和管理锁定异常的解决方案有什么问题?

Answers:


89

埃里克·利珀特(Eric Lippert)在他的博客中谈到了这一点: 锁和异常不会混在一起

等效代码在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,另一个是早期版本使用的示例。
CodesInChaos 2011年

附带说明一下,C#通过CLR提到了lock关键字的警告:您可能经常想在释放锁之前做一些恢复损坏状态(如果适用)的操作。由于lock关键字不允许我们将内容放入catch块中,因此我们应该考虑为非平凡的例程编写长版本try-catch-finally。
kizzx2'2

5
IMO恢复共享状态与锁定/多线程正交。因此,应在lock块内尝试捕获/最终完成。
CodesInChaos 2011年

2
@ kizzx2:这种模式在读写器锁的情况下特别好。如果在持有读取器锁的代码中发生异常,则没有理由指望受保护的资源可能已损坏,因此也没有理由使它无效。如果在写锁中发生异常,并且异常处理代码未明确表示受保护对象的状态已修复,则表明该对象可能已损坏,应该无效。恕我直言,意外的异常不应使程序崩溃,而应使可能损坏的任何内容无效。
2013年

2
@ArsenZahray您不需要Pulse简单的锁定。在某些高级多线程方案中,这一点很重要。我从来没有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());

错误:

70783sTUDIES.exe中发生类型'System.Threading.SynchronizationLockException'的异常,但未在用户代码中处理

附加信息:从未同步的代码块中调用了对象同步方法。

这是因为Monitor.Exit(lockObject);将对不可lockObject更改的内容进行操作,因为strings它们是不可变的,因此您是从不同步的代码块中调用它的。这只是一个有趣的事实。


“这是因为Monitor.Exit(lockObject);将作用于lockObject”。那么锁对对象没有任何作用?锁如何工作?
Yugo Amaryl

@YugoAmaryl,我想这是因为lock语句牢记首先传递的引用,然后使用它而不是使用更改的引用,例如:object temp = lockObject; Monitor.Enter(temp); <...locked code...> Monitor.Exit(temp);
Zhuravlev A.19.7.11

3

两者都是同一回事。lock是c sharp关键字,并使用Monitor类。

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


3
请查看msdn.microsoft.com/zh-cn/library/ms173179(v=vs.80).aspx “实际上,lock关键字是通过Monitor类实现的。例如”
RobertoBr,2011年

1
锁的基本实现使用Monitor,但它们不是相同的,考虑Monitor提供的锁不存在的方法,以及在单独的代码块中进行锁定和解锁的方法。
eran otzap

3

监视器的锁定和基本行为(输入+退出)大致相同,但是监视器具有更多选项,使您可以进行更多同步。

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

如果需要更多控制,最好使用显示器。您可以将Wait,TryEnter和Pulse用于高级用法(例如屏障,信号量等)。


1

Lock Lock关键字可确保一个线程同时执行一段代码。

锁(lockObject)

        {
        //   Body
        }

通过获取给定对象的互斥锁,执行一条语句然后释放该锁,关键字lock将语句块标记为关键部分

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

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

它提供了对对象的独占锁定,因此在任何给定时间点只能有一个线程进入关键部分。

Monitor和C#中的锁定之间的区别

锁定是Monitor的快捷方式,请尝试并最终输入。锁处理尝试并最终在内部阻塞 Lock = Monitor +最终尝试。

如果你想要更多的控制使用,以实现先进的多线程解决方案TryEnter() Wait()Pulse()PulseAll()方法,然后将显示器类是你的选择。

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

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

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


0

除了上述所有说明之外,lock是C#语句,而Monitor是System.Threading命名空间中的.NET类。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.