Nice programing

요청 된 순서대로 lock () 보증을 받습니까?

nicepro 2020. 12. 8. 19:59
반응형

요청 된 순서대로 lock () 보증을 받습니까?


여러 스레드가 동일한 개체에 대한 잠금을 요청하는 경우 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 회 수면

나가는 자물쇠 1 개

2 잠금

5001 틱 동안 2 회 수면

2 나가는 자물쇠

3 잠금

3 회 5001 틱

3 나가는 자물쇠

4 잠금

4 5001 틱 동안 수면

4 나가는 자물쇠

5 잠금

5001 틱 동안 5 회 수면

5 나가는 자물쇠

6 잠금

6 나가는 자물쇠

7 잠금

7 나가는 자물쇠

8 잠금

8 나가는 자물쇠

9 잠금

9 나가는 자물쇠


IIRC, it's highly likely to be in that order, but it's not guaranteed. I believe there are at least theoretically cases where a thread will be woken spuriously, note that it still doesn't have the lock, and go to the back of the queue. It's possible that's only for Wait/Notify, but I have a sneaking suspicion it's for locking as well.

I definitely wouldn't rely on it - if you need things to occur in a sequence, build up a Queue<T> or something similar.

EDIT: I've just found this within Joe Duffy's Concurrent Programming on Windows which basically agrees:

Because monitors use kernel objects internally, they exhibit the same roughly-FIFO behavior that the OS synchronization mechanisms also exhibit (described in the previous chapter). Monitors are unfair, so if another thread tries to acquire the lock before an awakened waiting thread tries to acquire the lock, the sneaky thread is permitted to acquire a lock.

The "roughly-FIFO" bit is what I was thinking of before, and the "sneaky thread" bit is further evidence that you shouldn't make assumptions about FIFO ordering.


Normal CLR locks are not guaranteed to be FIFO.

But, there is a QueuedLock class in this answer which will provide a guaranteed FIFO locking behavior.


The lock statement is documented to use the Monitor class to implement it's behavior, and the docs for the Monitor class make no mention (that I can find) of fairness. So you should not rely on requested locks being acquired in the order of request.

In fact, an article by Jeffery Richter indicates in fact lock is not fair:

Granted - it's an old article so things may have changed, but given that no promises are made in the contract for the Monitor class about fairness, you need to assume the worst.


Slightly tangential to the question, but ThreadPool doesn't even guarantee that it will execute queued work items in the order they are added. If you need sequential execution of asynchronous tasks, one option is using TPL Tasks (also backported to .NET 3.5 via Reactive Extensions). It would look something like this:

    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();
    }

This has a few advantages:

  1. Execution order is guaranteed.

  2. You no longer require an explicit lock (the TPL takes care of those details).

  3. You no longer need events and no longer need to wait on all events. You can simply say: wait for the last task to complete.

  4. If an exception were thrown in any of the tasks, subsequent tasks would be aborted and the exception would be rethrown by the call to Wait. This may or may not match your desired behavior, but is generally the best behavior for sequential, dependent tasks.

  5. By using the TPL, you have added flexibility for future expansion, such as cancellation support, waiting on parallel tasks for continuation, etc.


I am using this method to do FIFO lock

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?");
            }
        }
    }
}

The ConcurrentQueue will order the execution of the actions while the threads are waiting in the lock.

참고URL : https://stackoverflow.com/questions/4228864/does-lock-guarantee-acquired-in-order-requested

반응형