当一个线程试图进入一个关键部分并获得一个锁时,它实际上在做什么?
我问这个是因为我经常创建一个对象(对象类型),它只用于锁定目的。 请考虑以下内容:我想编写一个接受集合的方法,以及一个将作为锁定对象的对象,以便该方法中的整个集合操作将在临界区内声明,该区域将被该给定对象锁定。 / p>
我应该使用“ref”传递该锁定对象还是传递该对象的引用副本就足够了?换句话说 - 因为lock语句仅用于引用类型,机制是否检查引用对象的值,还是检查指针的值?因为很明显,当传递一个没有“ref”的对象时,我实际上得到了一个引用的副本,而不是引用本身。
答案 0 :(得分:6)
这是锁定时可以遵循的典型模式。基本上,您可以创建一个锁定对象,用于锁定对关键部分的访问(正如@Hans所说,它不保护您正在处理的对象 - 它只是处理锁定)。
class ThreadSafe
{
static readonly object _locker = new object();
static int _val1, _val2;
static void Go()
{
lock (_locker)
{
if (_val2 != 0) Console.WriteLine (_val1 / _val2);
_val2 = 0;
}
}
}
此示例来自线程上的Joseph Albahari's online book。它可以很好地概述您在创建lock
语句时所发生的情况以及有关如何最佳地优化它的一些提示/技巧。绝对强烈推荐阅读。
Per Albahari再次将lock
语句在.NET 4中翻译为:
bool lockTaken = false;
try
{
Monitor.Enter (_locker, ref lockTaken);
// Do your stuff...
}
finally { if (lockTaken) Monitor.Exit (_locker); }
它实际上比直接Monitor.Enter
更安全,然后在Monitor.Exit
中调用finally
,这就是它在.NET 4中添加的原因。
答案 1 :(得分:3)
只需锁定object
而不通过ref
即可。 lock
实际上做的是,打电话
在块的开头Monitor.Enter和退出时Monitor.Exit。
希望这有帮助。
答案 2 :(得分:1)
MSDN说here关于锁定
使用Enter获取作为参数传递的对象的监视器。如果另一个 线程已在对象上执行了Enter但尚未执行相应的Exit, 当前线程将阻塞,直到另一个线程释放该对象。这是合法的 在没有阻塞的情况下调用不止一次的同一个线程;然而,平等 必须在等待对象的其他线程之前调用Exit调用次数 解锁。
这意味着它不是关于引用所指向的实际对象的引用或指针,因此您不需要传递ref
简单传递引用将起作用
关于锁内部实际发生的事情,请参阅this问题的回答
“锁定语句由C#转换为以下内容:”
var temp = obj;
Monitor.Enter(temp);
try
{
// body
}
finally
{
Monitor.Exit(temp);
}
答案 3 :(得分:1)
我应该使用“ref”传递该锁定对象还是传递该对象的引用副本就足够了?
可能都没有。如果您有一些非线程安全的资源,最好的选择通常是直接从一个类访问该资源,该类具有锁定对象作为字段(或者您可以直接锁定资源)。如果您将锁定对象传递给其他人,则很难确保代码仍能正常工作,例如锁定在应该完成时完成并且没有死锁。
但是如果你真的想要传递锁对象,就不需要像其他人指出的那样使用ref
。锁定是在对象的实例上完成的,而不是在包含对它的引用的变量上完成的。