I've written a lot of multi-threaded C# code, and I've never had a deadlock in any code I've released.
I use the following rules of thumb:
lock
keyword (I also use other techniques such as reader/writer locks, but sparingly, and only if required for speed).Interlocked.Increment
if I am dealing with a long
.long
, dictionary
or list
.I'm wondering if it's even possible to generate a deadlock if these rules are thumb are consistently followed, and if so, what the code would look like?
Update
I also use these rules of thumb:
TimeSpan
. object _lockDict = new object();
then lock(_lockDict) { // Access dictionary here }
.Update
Great answer from Jon Skeet. It also confirms why I never get deadlocks as I tend to instinctively avoid nested locks, and even if I do use them, I've always instinctively kept the entry order consistent.
And in response to my comment on tending to use nothing but the lock
keyword, i.e. using Dictionary
+ lock
instead of ConcurrentDictionary
, Jon Skeet made this comment:
@Contango: That's exactly the approach I'd take too. I'd go for simple code with locking over "clever" lock-free code every time, until there's evidence that it's causing an issue.
Yes, it's easy to deadlock, without actually accessing any data:
private readonly object lock1 = new object();
private readonly object lock2 = new object();
public void Method1()
{
lock(lock1)
{
Thread.Sleep(1000);
lock(lock2)
{
}
}
}
public void Method2()
{
lock(lock2)
{
Thread.Sleep(1000);
lock(lock1)
{
}
}
}
Call both Method1
and Method2
at roughly the same time, and boom - deadlock. Each thread will be waiting for the "inner" lock, which the other thread has acquired as its "outer" lock.
If you make sure you always acquire locks in the same order (e.g. "never acquire lock2
unless you already own lock1
) and release the locks in the reverse order (which is implicit if you're acquiring/releasing with lock
) then you won't get that sort of deadlock.
You can still get a deadlock with async code, with just a single thread involved - but that involves Task
as well:
public async Task FooAsync()
{
BarAsync().Wait(); // Don't do this!
}
public async Task BarAsync()
{
await Task.Delay(1000);
}
If you run that code from a WinForms thread, you'll deadlock in a single thread - FooAsync
will be blocking on the task returned by BarAsync
, and the continuation for BarAsync
won't be able to run because it's waiting to get back onto the UI thread. Basically, you shouldn't issue blocking calls from the UI thread...
See more on this question at Stackoverflow