lock()保证是否按请求获得了?

时间:2010-11-19 19:57:17

标签: c# .net synchronization locking

当多个线程请求锁定同一个对象时,CLR是否保证按照请求的顺序获取锁?

我写了一个测试,看看这是否属实,似乎表明是,但我不确定这是否是确定的。

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退出锁

     

1获得锁定

     

1睡眠5001蜱

     

1退出锁

     

2得到了锁定

     

2睡觉5001蜱

     

2退出锁

     

3得到了锁定

     

3睡5001蜱

     

3退出锁

     

4得到了锁定

     

4睡5001蜱

     

4退出锁

     

5获得了锁定

     

5睡觉5001蜱

     

5退出锁

     

6得到了锁定

     

6退出锁

     

7获得了锁定

     

7退出锁

     

8获得了锁定

     

8退出锁定

     

9获得了锁定

     

9退出锁

5 个答案:

答案 0 :(得分:73)

IIRC,它很可能按顺序排列,但不能保证。我相信至少在理论上情况下,一个线程将被虚假地唤醒,注意它仍然没有锁定,并且转到队列的后面。这可能仅适用于Wait / Notify,但我也有一种潜在的怀疑也是为了锁定。

绝对不会依赖它 - 如果您需要在序列中发生事情,请构建Queue<T>或类似的东西。

编辑:我刚刚在Joe Duffy的Concurrent Programming on Windows内找到了这个基本同意的内容:

  

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

“粗略FIFO”位是我之前想到的,“偷偷摸摸的线程”位进一步证明你不应该对FIFO排序做出假设。

答案 1 :(得分:10)

记录lock语句使用Monitor类来实现它的行为,而Monitor类的文档没有提及(我能找到)公平性。因此,您不应该依赖请求顺序获取请求的锁。

事实上,Jeffery Richter撰写的一篇文章实际上表明lock不公平:

当然 - 这是一篇旧文章所以事情可能已经发生了变化,但鉴于Monitor课程中关于公平的合同没有做出任何承诺,你需要承担最坏的情况。

答案 2 :(得分:10)

正常的CLR锁定不保证是FIFO。

但是,有一个QueuedLock class in this answer 可以提供有保障的FIFO锁定行为

答案 3 :(得分:2)

与问题略有不同,但ThreadPool甚至不保证它将按添加顺序执行排队的工作项。如果需要顺序执行异步任务,一个选项是使用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. 您不再需要显式锁定(TPL负责处理这些细节)。

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

  4. 如果在任何任务中抛出异常,后续任务将被中止,并且调用Wait将重新抛出异常。这可能与您期望的行为相匹配,也可能不相符,但通常是顺序相关任务的最佳行为。

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

答案 4 :(得分:0)

我正在使用此方法进行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将命令执行操作。