代码之家  ›  专栏  ›  技术社区  ›  Samuel Neff

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

  •  59
  • Samuel Neff  · 技术社区  · 14 年前

    当多个线程请求同一对象上的锁时,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睡5001次

    1退出锁定

    2号锁

    2睡5001个

    2出口锁

    3号锁上了

    3个睡了5001个虱子

    3出口锁

    4号锁

    睡了5001个虱子

    4出口锁

    5号锁上了

    5个睡了5001个虱子

    5出口锁

    6号锁上了

    6出口锁

    7号锁上了

    7出口锁

    8号锁上了

    8出口锁

    9号锁上了

    9出口锁

    5 回复  |  直到 14 年前
        1
  •  73
  •   Jon Skeet    14 年前

    极有可能 Wait / Notify ,但我有一个潜移默化的怀疑,这也是为了锁定。

    一定地 不会依赖它-如果你需要事情按顺序发生,建立一个 Queue<T> 或者类似的东西。

    编辑:我刚在乔·达菲家找到这个 Concurrent Programming on Windows 基本同意:

    由于监视器在内部使用内核对象,因此它们显示的FIFO行为与OS同步机制所显示的大致相同(在前一章中进行了描述)。监视器是不公平的,因此如果另一个线程在唤醒的等待线程试图获取锁之前尝试获取锁,则允许偷偷摸摸的线程获取锁。

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

        2
  •  11
  •   Community CDub    8 年前

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

    但是,有一个 QueuedLock class in this answer 它将提供有保证的FIFO锁定行为 .

        3
  •  10
  •   Michael Burr    14 年前

    这个 lock 声明被记录为使用 Monitor 类来实现它的行为,而Monitor类的文档没有提到(我可以找到)公平性。因此,您不应该依赖按请求顺序获取请求的锁。

    事实上,杰弗里里克特的一篇文章表明 不公平:

    当然-这是一篇老文章,所以事情可能已经改变了,但鉴于合同中没有对 监视器 关于公平的课,你需要做最坏的假设。

        4
  •  2
  •   Dan Bryant    14 年前

    与问题稍微有点相悖,但线程池甚至不能保证它将按添加的顺序执行排队的工作项。如果您需要异步任务的顺序执行,一个选项是使用TPL任务(也可以通过 Reactive Extensions ). 它看起来像这样:

        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,您为将来的扩展增加了灵活性,例如取消支持、等待并行任务继续进行等。

        5
  •  0
  •   Noam    9 年前

    我用这个方法来做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将命令执行操作。