Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
207 views
in Technique[技术] by (71.8m points)

c# - Do embedded locks add any value against race conditions?

I've been facing a few race conditions in my code lately and I'm wondering if secondary/tertiary locks actually add any value. They seem very redundant, and this SO post seems to align with my thought process on it by stating:

In order to prevent race conditions from occurring, you would typically put a lock around the shared data to ensure only one thread can access the data at a time. This would mean something like this:

obtain lock for x ... release lock for x

Given this simple example to remove empty queues from a collection:

Dictionary<Guid, Queue<int>> _queues = new Dictionary<Guid, Queue<int>>();

...

lock (_queues) {
    while (_queues.Any(a => a.Value.Count == 0)) {
        Guid id = _queues.First(f => f.Value.Count == 0);
        if (_queues.ContainsKey(id))
            lock (_queues)
                _queues.Remove(id);
    }
}

Does the second lock provide any value?

question from:https://stackoverflow.com/questions/65834983/do-embedded-locks-add-any-value-against-race-conditions

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Does the second lock provide any value?

The answer is obviously no, your intuitions are correct, but let's rationalize why.

The way a lock works is by calling Monitor.Enter/Exit.

  • When you take a lock the CLR marks the header of the object (or its link to the sync table) atomically with a thread Id and a counter.

  • If another thread tries to lock over the object with an existing Thread ID

    1. It will perform a small spin wait (thin lock) to efficiently await the release without a context switch
    2. Failing that, it will take a more aggressive approach to promote the lock to an event with a operating system and wait on that handle (fat lock).
  • Each time the same thread calls the same lock on the same object, it (in essence) just increments the counter of the object in the header or (sync block) and continues unabated until it exits the lock (Monitor.Exit) at which point it decrements the counter. So on and so forth until the counter is zero.

So... do two nested locks on the same object in the same body of single threaded code achieve anything? Well the answer is no, apart from incrementing the lock counter (at a small cost).

So you might ask the question, what is the use of all this counter'ing? Well, it's actually for more complicated reentrant code scenarios, or where you have branching that may redirect the code to a lock over the same object... In that case, the same thread will not block on the same lock, in turn negating a very real deadlock situation.

Note : There are some components of how internal locking work that is CLR implementation and operating system specific, however the ECMA specs guarantees a certain consistent implementation of how these synchronization primitives' need to work in regard to behavior, reentrance, structural/emitted code and jit reordering.


Additional resources

For all the gory details you can see my answer here

Why Do Locks Require Instances In C#?


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...