lock()保证是否按顺序获取?

当多个线程在同一个对象上请求一个锁时,CLR是否保证锁将按照它们被请求的顺序被获取?

我写了一个testing,看看这是否是真实的,似乎表明是的,但我不确定这是否是确定性的。

class LockSequence { private static readonly object _lock = new object(); private static DateTime _dueTime; public static void Test() { var states = new List<State>(); _dueTime = DateTime.Now.AddSeconds(5); for (int i = 0; i < 10; i++) { var state = new State {Index = i}; ThreadPool.QueueUserWorkItem(Go, state); states.Add(state); Thread.Sleep(100); } states.ForEach(s => s.Sync.WaitOne()); states.ForEach(s => s.Sync.Close()); } private static void Go(object state) { var s = (State) state; Console.WriteLine("Go entered: " + s.Index); lock (_lock) { Console.WriteLine("{0,2} got lock", s.Index); if (_dueTime > DateTime.Now) { var time = _dueTime - DateTime.Now; Console.WriteLine("{0,2} sleeping for {1} ticks", s.Index, time.Ticks); Thread.Sleep(time); } Console.WriteLine("{0,2} exiting lock", s.Index); } s.Sync.Set(); } private class State { public int Index; public readonly ManualResetEvent Sync = new ManualResetEvent(false); } } 

打印:

进入:0

0有锁

0睡觉49979998蜱

进入:1

进入:2

进入:3

进入:4

进入:5

进入:6

进入:7

进入:8

进入:9

0退出locking

1有锁

睡1 5001蜱

1退出locking

2有锁

2睡5001蜱

2退出locking

3有锁

3睡5001蜱

3退出locking

4有锁

4睡5001蜱

4退出locking

5有锁

5睡5001蜱

5退出locking

6有锁

6退出locking

7有锁

7退出locking

8有锁

8退出locking

9有锁

9退出locking

IIRC,这是很有可能的顺序,但不能保证。 我相信至less在理论上,一个线程会被虚假地唤醒的情况下,请注意它仍然没有锁,然后到队列的后面。 这可能是只有Wait / Notify ,但我有一个偷偷摸摸的locking以及。

绝对不会依赖它 – 如果你需要在一个序列中发生的事情,build立一个Queue<T>或类似的东西。

编辑:我刚刚发现这在乔Duffy的并行编程在Windows基本上同意:

因为监视器在内部使用内核对象,所以它们performance出与OS同步机制同样performance出的粗略的FIFO行为(在前面的章节中描述过)。 监视器是不公平的,所以如果另一个线程在唤醒的等待线程尝试获取锁之前尝试获取锁,则允许偷偷摸摸的线程获取锁。

“粗略的先进先出”位是我之前想到的,而“偷偷摸摸的线程”位则进一步certificate了你不应该对FIFOsorting做出假设。

lock语句被logging为使用Monitor类来实现它的行为,而Monitor类的文档没有提到(我可以find)公平性。 所以你不应该依赖请求的顺序获取请求的锁。

事实上,Jeffery Richter的一篇文章指出其实lock是不公平的:

  • .NET CLR中的线程同步公平性

当然 – 这是一篇老文章,所以事情可能已经发生了变化,但鉴于Monitorclass合同中没有关于公平的承诺,你需要承担最坏的一面。

正常的CLR锁不保证是FIFO。

但是, 这个答案中有一个QueuedLock类 ,它将提供有保证的FIFOlocking行为

稍微切题,但是ThreadPool甚至不保证它会按照添加的顺序执行排队的工作项目。 如果您需要按顺序执行asynchronous任务,则有一个选项是使用TPL任务(也通过Reactive Extensions反向移植到.NET 3.5)。 它看起来像这样:

  public static void Test() { var states = new List<State>(); _dueTime = DateTime.Now.AddSeconds(5); var initialState = new State() { Index = 0 }; var initialTask = new Task(Go, initialState); Task priorTask = initialTask; for (int i = 1; i < 10; i++) { var state = new State { Index = i }; priorTask = priorTask.ContinueWith(t => Go(state)); states.Add(state); Thread.Sleep(100); } Task finalTask = priorTask; initialTask.Start(); finalTask.Wait(); } 

这有几个好处:

  1. 执行顺序是有保证的。

  2. 你不再需要明确的locking(TPL负责这些细节)。

  3. 你不再需要事件,不再需要等待所有事件。 你可以简单地说:等待最后一个任务完成。

  4. 如果在任何任务中抛出exception,则后续任务将被中止,并且通过调用Wait来重新抛出exception。 这可能会或可能不符合您所期望的行为,但通常是顺序,依赖任务的最佳行为。

  5. 通过使用TPL,您可以为未来的扩展增加灵活性,例如取消支持,等待并行任务的继续等。

我正在使用这个方法来做FIFO锁

 public class QueuedActions { private readonly object _internalSyncronizer = new object(); private readonly ConcurrentQueue<Action> _actionsQueue = new ConcurrentQueue<Action>(); public void Execute(Action action) { // ReSharper disable once InconsistentlySynchronizedField _actionsQueue.Enqueue(action); lock (_internalSyncronizer) { Action nextAction; if (_actionsQueue.TryDequeue(out nextAction)) { nextAction.Invoke(); } else { throw new Exception("Something is wrong. How come there is nothing in the queue?"); } } } } 

当线程在锁中等待时,ConcurrentQueue将命令执行这些动作。