friartuck
friartuck

Reputation: 3121

Threadpool management of shared variables in .NET

Let's say I have a timer (e.g. a System.Timers.Timer), and we know each elasped event will get put into the threadpool. If events come rapidly enough, how does the threadpool manage access to shared variables (e.g. a global int counter). Does the manager use semaphores/locks under the hood?

Or does it not do anything, and just simply make a copy of shared variables at the start of the threadpool, and the last thread to finish will set the correct variable value?

Unfortunately I can't really test this because the order of events firing are not guaranteed (e.g. using a counter variable is not reliable) between each elapsed event, as they may be fired out of order.

Thanks

Upvotes: 1

Views: 762

Answers (2)

Contango
Contango

Reputation: 80262

You have to manage multi-threaded access to shared variables yourself.

There are many answers on StackOverflow and Google explaining how to do this, search for "thread safety C#".

I've worked on huge projects with many potential threading issues, and the code I write just works. I'm damn good at writing thread safe code these days, as I've already made all of the possible mistakes.

If you are just learning to write thread safe code, then its easy to get overwhelmed by the huge amount of information out there. You might find some pages that cover the 8 different types of synchronization primitives. You will find huge discussions on the topic, and only half of them will be helpful.

If you are following the learning curve for the first time, I would recommend that you ignore said noise for now, and instead focus on mastering these two rules first:

Rule 1

If any two threads write to some shared primitive (like a long or a Dictionary or a List), put a lock around the access to this shared primitive. Aim for a situation so that when the lock is finished, the data structure is completely updated. This is the heart of writing thread safe code: all other rules for threading can be derived from this one.

Example:

// This _lock should be initialized once on program startup, and should be global.
static readonly object _dictLock = new object();    

// This data structure can be accessed by multiple threads.
public static Dictionary<string, int> dict = new Dictionary<string, int>();

lock (_dictLock)
{
    if (dict.ContainsKey("Hello") == false)
    {
        dict.Add("Hello", 42);
    }
} // Lock exits: data structure is now completely 100% updated. Google "atomic access C#".

Rule 2

Try not to have locks within locks. This can create deadlocks if the locks are entered in the wrong order. If you only lock around the primitives (e.g. dictionary, long, string, etc), then this shouldn't be an issue.

Guideline 1

If you are just learning, use nothing but lock, see how to use lock. Its difficult to go wrong if you just this, as the lock is automatically released when the function exits. You can graduate to other types of locks, like reader-write locks, later on. Don't bother with ConcurrentDictionary or Interlocked.Increment yet - focus on getting the basics correct.

Guideline 2

Try to spend as little time in locks as possible. Don't put a lock around a huge block of code, put locks around the smallest possible portions in the code, usually a dictionary or a long. A lock is blindingly fast unless its contested, so this technique seems to work well to create thread safe code that is fast.

Cause of 95% of meaningful threading issues?

In my experience, the single biggest cause of thread-unsafe code is Dictionary. Even ConcurrentDictionary is not immune to this - it needs manual locking to be correct if the access is spread over multiple lines. If you get this right, you will eliminate 95% of meaningful threading issues in your code.

Upvotes: 2

usr
usr

Reputation: 171178

The thread pool can't magically make your shared mutable variables thread-safe. It has no control over them and it does not even know they exist.

Be aware of the fact that timer ticks can happen concurrently (even at low frequencies) and after the timer has been disposed. You need to perform any synchronization necessary.

The thread pool itself is thread-safe in the sense that I can successfully process concurrent work items (which is kind of the point).

Upvotes: 0

Related Questions