我要求一部分代码一次只能由一个线程运行(单个资源锁定)。
C#中的lock(object)
语句允许这样做。但是,它不会保留对锁的请求顺序。
例如,考虑以下100个线程启动,其中编号的线程尝试按顺序锁定挂锁:
for (int i = 0; i < 100; i++)
{
(new Thread(delegate(object index)
{
int name = (int) index;
byte[] len = new byte[2];
Console.WriteLine(string.Format("Thread:{0} going for lock. ",
name));
lock (padlock)
{
rnd.GetBytes(len);
ushort l = BitConverter.ToUInt16(len, 0);
Console.WriteLine(string.Format("Thread:{0} sleeping: {1}",
name, l));
Thread.Sleep(l);
}
})).Start(i);
实际授予访问权限的顺序不完整(1-> 100)或NOT FIFO。然而,似乎确实存在“早期早期”EIEO模式(可能由堆运行?)。
问题:是什么决定了锁定授予顺序和可以依赖它而不是让一个不幸的线程挨饿?
更新:this answer解释了这一点。这是相关的引用(Joe Duffy在Windows上的并发编程):
因为监视器在内部使用内核对象,所以它们表现相同 操作系统同步机制也具有粗略的FIFO行为 展览(在前一章中描述)。监视器是不公平的,所以 如果另一个线程试图在唤醒等待之前获取锁定 线程尝试获取锁,允许偷偷摸摸的线程 获得一把锁。
答案 0 :(得分:5)
Servy的答案当然是正确的。一些额外的细节:
什么决定锁定授予顺序?
最终,操作系统。
是不是可以依赖不挨饿的一个不幸的线程?
饥饿不太可能,但可能。如果你不能忍受很小的饥饿机会,你需要的东西比锁定更复杂。
我还注意到锁是“不公平的”,因为你可以在锁中有一个线程,8个线程在等待,锁中的线程离开,还有一个没有等待的第十个线程请求锁定并获取它,有效地“切断线”。 Joe给出了一个有趣的分析,为什么Windows现在在这里使用“不公平”的锁定分配策略,如果这个主题感兴趣的话:
http://joeduffyblog.com/2006/12/14/anticonvoy-locks-in-windows-server-2003-sp1-and-windows-vista/
答案 1 :(得分:3)
决定锁定授予顺序的原因
规格中没有定义订单。你不能依赖任何订单。
可以依靠不会让一个不幸的线程挨饿吗?
没有。如果您的应用程序存在潜在问题,则需要手动管理。
另请注意,由于几个不同的原因,线程将运行的顺序未确定。您知道所有线程都将按特定顺序启动,但一旦启动,您就不知道它们将如何安排。他们每个人都可以按任何顺序点击lock
块。即使它是基于它们何时达到lock
的FIFO,它仍然是未定义的。