监视与锁定

sma*_*man 83 .net c# multithreading locking monitor

何时在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 ?
    }
}
Run Code Online (Sandbox Code Playgroud)

更新

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

Cod*_*aos 87

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);
}
Run Code Online (Sandbox Code Playgroud)

它取决于Monitor.Enter锁定时原子设置标志.


早些时候它是:

var temp = obj;
Monitor.Enter(temp);
try
{
   body
}
finally
{
    Monitor.Exit(temp);
}
Run Code Online (Sandbox Code Playgroud)

这依赖于也不例外之间被抛出Monitor.Entertry.我认为在调试代码中这种情况被违反了,因为编译器在它们之间插入了一个NOP,从而在那些可能之间进行了线程中断.

  • IMO恢复共享状态与锁定/多线程正交.所以它应该在`lock`块中使用try-catch/finally来完成. (5认同)
  • @ kizzx2:这样的模式对于读写器锁会特别好.如果在包含读取器锁定的代码中发生异常,则没有理由期望受保护的资源可能被损坏,因此没有理由使其无效.如果在写入器锁中发生异常并且异常处理代码没有明确指示受保护对象的状态已被修复,则表明该对象可能已损坏且应该无效.恕我直言,意外的异常不应该使程序崩溃,但应该使任何可能损坏的东西无效. (2认同)
  • @ArsenZahray你不需要`Pulse`来简单锁定.这在一些先进的多线程场景中很重要.我从未直接使用过`Pulse`. (2认同)

Luk*_*tný 41

lock只是Monitor.Entertry+ finally和的快捷方式Monitor.Exit.只要它足够就使用lock语句 - 如果你需要像TryEnter这样的东西,你将不得不使用Monitor.


She*_*Pro 20

锁定语句相当于:

Monitor.Enter(object);
try
{
   // Your code here...
}
finally
{
   Monitor.Exit(object);
}
Run Code Online (Sandbox Code Playgroud)

但是,请记住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); 
} 
Run Code Online (Sandbox Code Playgroud)

Thanx到CodeInChaos的评论和链接


Ale*_*lex 9

Monitor更灵活.对我来说,最喜欢使用显示器的情况是当你不想等到轮到你时,只需跳过:

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


And*_*ena 6

正如其他人所说,lock"等同于"

Monitor.Enter(object);
try
{
   // Your code here...
}
finally
{
   Monitor.Exit(object);
}
Run Code Online (Sandbox Code Playgroud)

但出于好奇,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());
Run Code Online (Sandbox Code Playgroud)

......而这不是:

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());
Run Code Online (Sandbox Code Playgroud)

错误:

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

附加信息:从非同步代码块调用对象同步方法.

这是因为因为是不可变的,所以Monitor.Exit(lockObject);行为lockObject已经改变了strings,然后你从一个不同步的代码块中调用它..但无论如何.这只是一个有趣的事实.