Вы находитесь на странице: 1из 14

Introduction

In this project I will discuss deadlocks, what they are and how they can be avoi
ded. I will also provide a sample implementation of how to detect when a deadloc
k occurs in a .NET application. The project uses the Dining Philosopher problem
as a basis for discussion.
Background
Avoiding a deadlock is in theory quite simple as there are four, well defined, c
onditions that must all hold true simultaneously for the code to deadlock. Preve
nting or avoiding a single one of these conditions stops a deadlock from happeni
ng, but deadlocks are still not uncommon in production code.
In this project I will be taking a fairly academic approach and the implementati
ons I provide to avoid the deadlock are simple and obvious, this is rarely the c
ase in production code but for clarity I have decided to keep things cut-down. I
t is very possible this approach makes it harder to translate the contents of th
is article to a real life scenario, for that I apologize.
Dining Philosophers
The Dining Philosophers problem is a classic problem used in computer science to
illustrate concurrency and locking problems, it was initially formulated by Eds
ger W. Dijkstra and Wikipedia has a detailed article on it, but in short it goes
something like this;
Five philosophers are sitting in front of five plates of food, between each plat
e is one fork. Each philosopher needs to think and eat but to eat they need to h
old two forks. Since the forks are shared between two plates a philosopher is on
ly able to grab a fork if it is not already held by another philosopher. After e
ating for a while the philosophers drops the forks making them available for som
eone else.
A diagram of the problem shows the five philosophers and the forks (and because
I can't do graphics or diagrams my forks looks like diamonds):
As can be seen from the diagram, if the green philosopher holds fork 1 and 2, th
en the yellow philosopher (for example) is unable to eat as he cannot grab fork
1, only fork 5.
Such an arrangement might lead to a deadlock as each philosopher might hold one
of their forks, but are unable to grab the second fork because it is already hel
d by someone else. As all philosophers are waiting for someone else to drop a fo
rk the system deadlocks and no philosopher will be able to grab a second fork or
drop the current one they hold.
Using this problem I'll show the cause of the deadlock and by preventing the Cof
fman Conditions, one by one, show how the application no longer deadlocks.
But let's start at the beginning with an application that does deadlock.
DiningPhilosophers.Deadlock
The project DiningPhilosophers.Deadlock contains two main classes; Fork and Phil
osopher and a Program module that sets up and starts the dining.
Fork
As a Fork must be held by only one Philosopher at a time it acquires an exclusiv
e lock by calling System.Threading.Monitor.Enter(object syncRoot), this locks th
e Fork to the thread on which the call to Fork.Grab is made (by a Philosopher).
syncRoot is owned by the Fork and is declared as Private ReadOnly syncRoot As Ob
ject = New Object().

After the Philosopher is done using the Fork it is dropped by calling Fork.Drop.
The Forks also have an internal identifier, Private ReadOnly id As Integer, this
is not important to the problem but is used to identify the Forks making it is
easier to see what's going on.
The Fork class looks like this;
Hide Copy Code
Imports System.Threading
Public Class Fork
Private Shared idCounter As Integer
Private ReadOnly id As Integer
Private ReadOnly syncRoot As Object = New Object()
Public Sub New()
idCounter = idCounter + 1
id = idCounter
End Sub
Public Overrides Function ToString() As String
Return Convert.ToString(id)
End Function
Public Sub Grab()
Monitor.Enter(syncRoot)
End Sub
Public Sub Drop()
Monitor.Exit(syncRoot)
End Sub
End Class
Philosopher
The Philosopher class takes a name and two Forks in it's constructor, it then us
es the Forks in it's Philosopher.Dine method to implement the problem scenario.
A Philosopher starts dining when Philosopher.Start is called, this method create
s a new System.Threading.Tasks.Task that runs the Dine method. It is important t
hat this runs on a thread different than the threads that run the other Philosop
her's Dine method as it's impossible to get into a deadlock situation on a singl
e thread.
The Philosopher.Dine method is just a loop that tries to grab the first Fork, th
en grab the second Fork and then drop them;
Hide Copy Code
Public Sub Dine()
While dining
Console.WriteLine("{0}
left.Grab()
Console.WriteLine("{0}
t, right)
right.Grab()
Console.WriteLine("{0}
left.Drop()
right.Drop()
Console.WriteLine("{0}
End While

is trying to grab fork {1}", name, left)


grabbed fork {1}, trying to grab fork {2}", name, lef
grabbed fork {1}, thinking for a bit", name, right)
dropped both forks", name)

End Sub
The part where the Philosopher thinks is essentially just the statement that out
puts that the Philosopher is thinking for a bit. This illustrates and important
point; it's not required to have long running or complex processing inside locke
d regions for a deadlock to occur, all it takes is the concurrent nature of mult
iple threads (or processes) and exclusive locking of resources.
This loop will run until someone call StopDining, or indeed until it deadlocks.
The full listing for the Philosopher class looks like this;
Hide Shrink
Copy Code
Public Class Philosopher
Private ReadOnly name As String
Private ReadOnly left As Fork
Private ReadOnly right As Fork
Private dining As Boolean
Public Sub New(name As String, left As Fork, right As Fork)
If name Is Nothing Then Throw New ArgumentNullException("name")
If left Is Nothing Then Throw New ArgumentNullException("left")
If right Is Nothing Then Throw New ArgumentNullException("right")
Me.name = name
Me.left = left
Me.right = right
End Sub
Public Sub Dine()
While dining
Console.WriteLine("{0}
left.Grab()
Console.WriteLine("{0}
t, right)
right.Grab()
Console.WriteLine("{0}
left.Drop()
right.Drop()
Console.WriteLine("{0}
End While
End Sub

is trying to grab fork {1}", name, left)


grabbed fork {1}, trying to grab fork {2}", name, lef
grabbed fork {1}, thinking for a bit", name, right)
dropped both forks", name)

Public Function Start() As Task


If dining Then Throw New InvalidOperationException("Cannot start dining when
already dining.")
dining = True
Return Task.Factory.StartNew(AddressOf Dine)
End Function
Public Sub StopDining()
If Not dining Then Throw New InvalidOperationException("Cannot stop dining w
hen not already dining.")
dining = False
End Sub
End Class
Setting up the scenario

The Program module sets up five Forks and five Philosophers and distributes the
Forks between the Philosophers.
It then calls Philosopher.StartDining on all Philosophers and runs the scenario
until a deadlock occurs. The Philosophers output to the console what they're doi
ng and when the output stops scrolling, that's when it's deadlocked.
In the image above the DiningPhilosophers.Deadlock project has deadlocked, and i
t is clear that all five philosophers are waiting for a Fork that is already tak
en.
The scenario is set up in Program and it looks like this;
Hide Copy Code
Module Program
''' <summary>
''' This program is expected to dead lock.
'''
</summary>
Simple, create 5 Forks, create 5 Philosophers that use the Forks and start the d
ining.
So now we have an application that lock up after a little while and it's time to
start looking at possible solutions by going through the four Coffman Condition
s and violating them by changing the implementation in such a way that a single
of the conditions no longer holds true.
Mutual Exclusion
At least two resources must be non-shareable. Only one process can use the resou
rce at any given instant of time.
Mutual Exclusion means that there must exist resources that consumers require bu
t cannot use at the same time, in Dining Philosophers this is of course the Fork
s, in a real life application it's likely not to be a fork implementation and ca
n be something like a database table or a file etc.
Note that non-sharable here does not mean that two consumers cannot use the same
resource, it only means that they cannot use the same resource at the same time
.
If a way can be found to negate the Mutual Exclusion condition then a deadlock c
annot occur, one way to achieve this is to saturate the system with enough resou
rces for no contention to take place. In the DiningPhilosophers.MutualExclusion
project the Fork and Philosopher classes are unchanged compared to the implement
ation in DiningPhilosophers.Deadlock, instead, the only changes made are in Prog
ram and the setup of the scenario.
There are essentially two ways of violating the Mutual Exclusion condition; make
the resources shareable or make sure they don't need to be shared and I've opte
d for the latter of these two. By providing two Forks for every Philosopher ther
e is no longer possible to get into a state where one Philosopher is waiting for
anothers Fork as there are always Forks available.
The setup looks like this;
Hide Shrink
Module Program

Copy Code

Sub Main()
' Setup ten forks
Dim fa1 = New Fork()

Dim
Dim
Dim
Dim
Dim
Dim
Dim
Dim
Dim

fa2
fb1
fb2
fc1
fc2
fd1
fd2
fe1
fe2

=
=
=
=
=
=
=
=
=

New
New
New
New
New
New
New
New
New

Fork()
Fork()
Fork()
Fork()
Fork()
Fork()
Fork()
Fork()
Fork()

Dim
Dim
Dim
Dim
Dim

philosopherA
philosopherB
philosopherC
philosopherD
philosopherE

=
=
=
=
=

New
New
New
New
New

Philosopher("Aristotle", fa1, fa2)


Philosopher("Plato", fb1, fb2)
Philosopher("Paul of Tarsus", fc1, fc2)
Philosopher("Rene Descartes", fd1, fd2)
Philosopher("Confucius", fe1, fe2)

philosopherA.Start()
philosopherB.Start()
philosopherC.Start()
philosopherD.Start()
philosopherE.Start()
Console.ReadLine()
End Sub
End Module
Running that program does not deadlock and it was very easy to implement the cha
nge, but in an actual application it is usually very difficult to just double up
on the resources so this approach in unlikely to be that useful. If an applicat
ion deadlocks when accessing the database, for example, one cannot simply add mo
re tables to get rid of the deadlock as that leads to all sorts of other issues.
Hold and Wait
A process is currently holding at least one resource and requesting additional r
esources which are being held by other processes.
Hold and Wait occurs in the Dining Philosophers problem when a Philosopher holds
one of his Forks and are trying to grab the second one whilst this is still hel
d by another Philosopher. The Philosopher Holds one Fork and Waits for the next
to be available.
One way to violate this condition could be to check if the second Fork that the
Philosopher is trying to grab is already held, and if so drop the first Fork ins
tead of waiting for the second whilst still holding the first.
In project DiningPhilosophers.HoldAndWait I have decided to solve this one in a
different way; a new syncRoot object was added as a Shared member to the Philoso
pher class and the call to Fork.Grab is done within a SyncLock scope;
Hide Shrink
Copy Code
Public Class Philosopher
' Shared by all philosophers, makes grabbing two forks an atomic action
Private Shared ReadOnly SyncRoot As Object = New Object()
Private ReadOnly name As String
Private ReadOnly left As Fork
Private ReadOnly right As Fork
Private dining As Boolean

Public Sub New(name As String, left As Fork, right As Fork)


If name Is Nothing Then Throw New ArgumentNullException("name")
If left Is Nothing Then Throw New ArgumentNullException("left")
If right Is Nothing Then Throw New ArgumentNullException("right")
Me.name = name
Me.left = left
Me.right = right
End Sub
Public Sub Dine()
While dining
Console.WriteLine("{0} is trying to grab fork {1}", name, left)
SyncLock SyncRoot
left.Grab()
Console.WriteLine("{0} grabbed fork {1}, trying to grab fork {2}", name, l
eft, right)
right.Grab()
Console.WriteLine("{0} grabbed fork {1}, thinking for a bit", name, right)
End SyncLock
left.Drop()
right.Drop()
Console.WriteLine("{0} dropped both forks", name)
End While
End Sub
Public Function Start() As Task
If dining Then Throw New InvalidOperationException("Cannot start dining when
already dining.")
dining = True
Return Task.Factory.StartNew(AddressOf Dine)
End Function
Public Sub StopDining()
If Not dining Then Throw New InvalidOperationException("Cannot stop dining w
hen not already dining.")
dining = False
End Sub
End Class
Doing this turns the act of grabbing the Forks into an atomic action, it's impos
sible to aquire one without being guaranteed exclusive access to the other and t
his prevents the application from deadlocking. This approach is slightly more ap
plicable to an actual application, but there are many situations where this is d
ifficult to achieve as well.
The observant reader will have noticed that a more optimized solution would have
had syncRoots that are shared between Philosophers that share Forks, for brevit
y I've left implementing that out.
No Preemption
The operating system must not de-allocate resources once they have been allocate
d; they must be released by the holding process voluntarily.
This condition simply states that resources cannot be stolen. If Philosopher A w
ants to grab a Fork held by Philosopher B then he must simply wait, there is no

way for A to force B to release it.


By allowing Forks to be stolen project DiningPhilosophers.NoPreemption prevents
deadlocking.
Stealing a held resource is somewhat complicated if transactional integrity must
be maintained, but in the sample application this is not a concern. The Fork cl
ass has an additional method, Steal that will cause the owning thread to abort.
As the Philosopher who owned the Fork has his thread aborted he catches that exc
eption (ThreadAbortException) and Fork.Drops both his Forks as well as reset the
thread abort so that he can continue dining.
If you put a breakpoint in the catch-statement that detects the Fork being stole
n it might look something like this;
This approach, while it is working, is not recommended. There are very few scena
rios where one can get predictable results after calling Thread.Abort and it sho
uld be avoided when possible to do so. The reason for this is that there's no wa
y of knowing exactly when the ThreadAbortException will be thrown so it is unkno
wn where in the processing the thread aborted.
The Fork class that allows stealing looks like this;
Hide Shrink
Copy Code
Imports System.Threading
Public Class Fork
Private Shared idCounter As Integer
Private ReadOnly id As Integer
Private ReadOnly syncRoot As Object = New Object()
Private owner As Thread
Public Sub New()
idCounter = idCounter + 1
id = idCounter
End Sub
Public Overrides Function ToString() As String
Return Convert.ToString(id)
End Function
Public Function Grab() As Boolean
Dim entered = Monitor.TryEnter(syncRoot)
If entered Then owner = Thread.CurrentThread
Return entered
End Function
Public Sub Steal()
If Not owner Is Nothing Then owner.Abort()
Monitor.Enter(syncRoot)
owner = Thread.CurrentThread
End Sub
Public Sub Drop()
If Monitor.IsEntered(syncRoot) Then Monitor.Exit(syncRoot)

End Sub
End Class
The changes to Philosopher that allows it to detect a Fork being stolen and reco
vering from it looks like this;
Hide Shrink
Copy Code
Imports System.Threading
Public Class Philosopher
Private ReadOnly name As String
Private ReadOnly left As Fork
Private ReadOnly right As Fork
Private dining As Boolean
Public Sub New(name As String, left As Fork, right As Fork)
If name Is Nothing Then Throw New ArgumentNullException("name")
If left Is Nothing Then Throw New ArgumentNullException("left")
If right Is Nothing Then Throw New ArgumentNullException("right")
Me.name = name
Me.left = left
Me.right = right
End Sub
Public Sub Dine()
While dining
Try
Console.WriteLine("{0} is trying to grab fork {1}", name, left)
If Not left.Grab() Then left.Steal()
Console.WriteLine("{0} grabbed fork {1}, trying to grab fork {2}", name,
left, right)
If Not right.Grab() Then right.Steal()
Console.WriteLine("{0} grabbed fork {1}, thinking for a bit", name, righ
t)
Catch ex As ThreadAbortException
Console.WriteLine("Someone stole a fork from {0}, dropping other fork as
well", name)
Thread.ResetAbort()
Finally
left.Drop()
right.Drop()
Console.WriteLine("{0} dropped both forks", name)
End Try
End While
End Sub
Public Function Start() As Task
If dining Then Throw New InvalidOperationException("Cannot start dining when
already dining.")
dining = True
Return Task.Factory.StartNew(AddressOf Dine)
End Function
Public Sub StopDining()
If Not dining Then Throw New InvalidOperationException("Cannot stop dining w
hen not already dining.")

dining = False
End Sub
End Class
Circular Wait
A process must be waiting for a resource which is being held by another process,
which in turn is waiting for the first process to release the resource. In gene
ral, there is a set of waiting processes, P = {P1, P2, ..., PN}, such that P1 is
waiting for a resource held by P2, P2 is waiting for a resource held by P3 and
so on until PN is waiting for a resource held by P1.
The Circular Wait condition states that if the order in which resources are grab
bed is such that a circular graph can exist, then a deadlock can occur. In the d
eadlocking sample application DiningPhilosophers.Deadlock the Forks are grabbed
in what can be a circular order:
Aristotle tries to grab Fork 1 then Fork 2.
Plato tries to grab Fork 2 then Fork 3.
Paul of Tarsus tries to grab Fork 3 then Fork 4.
Rene Descartes tries to grab Fork 4 then Fork 5.
Confucius tries to grab Fork 5 then Fork 1.
As this forms a circular chain the Circular Wait condition is fulfilled and the
application can deadlock.
Simply put, if the resources in a system can be numbered (by some arbitrary numb
er) then make sure you grab the resources in order. In the sequence above Confuc
ius grabs 5 before 1 which is, well, out of order.
To violate this condition the only thing that needs to change is the setup, or P
rogram module, and it's a one line change. By changing the order of the Forks pa
ssed to Confucius from;
Hide Copy Code
Dim philosopherE = New Philosopher("Confucius", fe, fa)
to
Hide Copy Code
Dim philosopherE = New Philosopher("Confucius", fa, fe)
no circular graph can be created and the deadlock is no longer possible.
Making sure resources are aquired in order and always in that order is a common
way employed to prevent deadlocking while hitting database tables.
Detection
Another way of dealing with deadlocks that falls outside of violating the Coffma
n Conditions is to detect when a deadlock occurs and then take some action to re
solve it. This is an approach taken by some databases for example, where the dea
dlock is detected and one of the processes involved is selected for eviction (se
e Avoiding Database Deadlocks).
The approach is somewhat cumbersome as there is no built-in way of detecting dea
dlock in .NET and it has to be either hand-rolled or delegated to some third-par
ty library. In both cases it's likely that it will affect not only the way exclu
sive locks are acquired but also the performance of acquiring them.
In the downloadable solutions there is a project called DiningPhilosophers.Detec
tion that depends on Bornander.Locking (also included in the solutions), and tha
t project illustrates using detection by implementing a custom lock mechanism ba
sed upon the standard Monitor class.
My Bornander.Locking library includes a class called CheckedLock which is intend
ed to replace the SyncLock scopes normally employed to aquire a lock and it impl

ements IDisposable so that it can be used as a scope in the same manner using th
e Using keyword. The internals of CheckedLock uses a module called LockManager t
o aquire and release the locks and that allows LockManager to build up a graph o
f all the locks aquired and which thread that owns them. It also tracks which th
read is waiting for which lock.
By checking the graph whenever a new request to aquire a lock is received by Loc
kManager it is possible for LockManager to detect when a deadlock would occur. W
hen a would-be deadlock is detected the LockManager throws a DeadLockException i
nstead of calling Monitor.Enter to aquire the lock. By doing so the calling code
has a chance to handle this exception and react to it.
The CheckedLock class is implemented like this;
Hide Shrink
Copy Code
Public NotInheritable Class CheckedLock : Implements IDisposable
Private ReadOnly syncRoot As Object
Private ReadOnly timeout As TimeSpan
Private disposed As Boolean
Public Sub New(syncRoot As Object, timeout As TimeSpan)
If syncRoot Is Nothing Then Throw New ArgumentNullException("syncRoot")
Me.syncRoot = syncRoot
Me.timeout = timeout
LockManager.Aquire(Me)
End Sub
Public Sub New(syncRoot As Object)
Me.New(syncRoot, System.Threading.Timeout.InfiniteTimeSpan)
End Sub
Public Overrides Function ToString() As String
Return syncRoot.ToString()
End Function
Public Overrides Function Equals(obj As Object) As Boolean
Dim other As CheckedLock = TryCast(obj, CheckedLock)
Return (Not other Is Nothing) And Object.ReferenceEquals(syncRoot, other.syn
cRoot)
End Function
Public Overrides Function GetHashCode() As Integer
Return syncRoot.GetHashCode()
End Function
Public Sub Dispose() Implements IDisposable.Dispose
If disposed Then Return
disposed = True
LockManager.Release(Me)
End Sub
Friend ReadOnly Property LockSyncRoot As Object
Get
Return syncRoot
End Get

End Property
Friend ReadOnly Property LockTimeout As Object
Get
Return timeout
End Get
End Property
End Class
The logic for detecting a deadlock in LockManger uses a class representing a nod
e in the lock-graph called GraphNode that wraps up the identity of who's holding
a lock and who's requesting that lock. LockManager is implemented like this;
Hide Shrink
Copy Code
Imports System.Threading
Friend Module LockManager
Private ReadOnly syncRoot As Object = New Object()
Private ReadOnly pendingLocks As HashSet(Of LockInstance) = New HashSet(Of Loc
kInstance)
Private ReadOnly heldLocks As HashSet(Of LockInstance) = New HashSet(Of LockIn
stance)
Friend Sub Aquire(lock As CheckedLock)
Dim instance = New LockInstance(lock)
Dim heldInstance As LockInstance
SyncLock syncRoot
heldInstance = heldLocks.SingleOrDefault(Function(li) li.CheckedLock.Equal
s(lock))
' Lock held and it's not by the current thread
If (Not heldInstance Is Nothing) Then
If Thread.CurrentThread.ManagedThreadId <> heldInstance.ThreadId Then
pendingLocks.Add(instance)
Dim lockStack = New Stack(Of GraphNode)
lockStack.Push(New GraphNode(heldInstance, instance))
TraverseLockGraph(heldInstance, lockStack)
End If
End If
End SyncLock
' TODO: CHECK: Can the lack of internal syncRoot here result in race-conditi
on?
Dim entered = Monitor.TryEnter(lock.LockSyncRoot, lock.LockTimeout)
SyncLock syncRoot
pendingLocks.Remove(instance)
If entered Then
If Not heldInstance Is Nothing Then
If heldInstance.ThreadId = Thread.CurrentThread.ManagedThreadId Then
heldInstance.Reenter()
Return ' No need to add it to the held list as it's already there
End If
End If
heldLocks.Add(instance)
Else

' Timed out trying to enter the monitor


Throw New TimeoutException(instance, heldInstance)
End If
End SyncLock
End Sub
Private Sub TraverseLockGraph(heldInstance As LockInstance, lockStack As Stack
(Of GraphNode))
For Each pending In pendingLocks.Where(Function(pl) pl.ThreadId = heldInstanc
e.ThreadId)
Dim held = heldLocks.SingleOrDefault(Function(hl) Object.ReferenceEquals(h
l.CheckedLock.LockSyncRoot, pending.CheckedLock.LockSyncRoot))
If Not held Is Nothing Then
lockStack.Push(New GraphNode(held, pending))
If held.ThreadId = Thread.CurrentThread.ManagedThreadId Then
Throw New DeadlockException(lockStack.Reverse())
Else
TraverseLockGraph(held, lockStack)
End If
lockStack.Pop()
End If
Next
End Sub
Friend Sub Release(lock As CheckedLock)
SyncLock syncRoot
Dim heldInstance = heldLocks.Single(Function(li) Object.ReferenceEquals(li
.CheckedLock.LockSyncRoot, lock.LockSyncRoot))
If heldInstance.DoExit() Then
heldLocks.Remove(heldInstance)
End If
Monitor.Exit(lock.LockSyncRoot)
End SyncLock
End Sub
End Module
The CheckedLock class is used by the Fork class in DiningPhilosophers.Detection
like this;
Hide Shrink
Copy Code
Imports System.Threading
Imports Bornander.Locking
Public Class Fork
Private Shared idCounter As Integer
Private ReadOnly id As Integer
Private ReadOnly syncRoot As Object = New Object()
Private lockWrapper As CheckedLock
Public Sub New()
idCounter = idCounter + 1
id = idCounter
syncRoot = Convert.ToString(id)
End Sub
Public Overrides Function ToString() As String

Return Convert.ToString(id)
End Function
Public Sub Grab()
lockWrapper = New CheckedLock(syncRoot)
End Sub
Public Sub Drop()
lockWrapper.Dispose()
End Sub
End Class
Note:
While this approach of dealing with deadlocks technically works it is not one th
at I would recommend. It suffers from the same problem as the No Preemption solu
tion where the thread is aborted in order to steal the lock; the point where the
deadlock is detected is unknown and because of this predictable results are har
d to come by. Yes, the deadlock is guaranteed to happen at the aquiring of a loc
k but if a thread needs to grab 15 locks it also needs to be able to handle the
deadlock situation in all of those 15 places. It makes for convoluted code and i
t is not a good solution I feel.
Points of Interest
Avoiding deadlocks in complex applications can be hard, even if in theory one on
ly needs to remember one of the Coffman Conditions. I think one of the reasons f
or this is that applications grow over time in unexpected directions and that ma
kes it hard for developers to gain the overall picture required to see the how t
he Coffman Conditions are upheld.
There are a few tips one can follow to avoid dead locks;
Never lock on public members
This includes locking on Me (this in C#) or on GetType(SomeType) (or typeof(Some
Type) in C#). Locking on public members means that you are not alone in deciding
when the lock will be aquired, and that means there might be some other place t
hat you are unaware of that is also locking on the same thing.
Be verbose with concurrency
If calling a method on your class spawns a new thread make sure the caller is aw
are of this. One way of doing that is returning the spawned Task or a WaitHandle
that sets when the thread is done.
If callers are aware of the concurrency incurred they're more likely to take pre
cautions to avoid threading issues such as deadlocks.
Always order the resources and aquire them in order
Doing this prevents the Circular Wait condition and it is probably one of the ea
siest ways to prevent a deadlock that is feasable in practice.
Note that this can be achieved on-the-fly even if the actual resources are unkno
wn at compile time as long as there is some way of ordering the resources, as de
sribed here in the Avoiding deadlock with arbitrary locks section.
Never call Overridable (virtual in C#) methods if you're currently holding a loc
k
The reason for this is that if a sub class that overrides the method decides to
grab a lock you might end up in a deadlock that you did not see coming as one ra
rely has control over all sub-classes.
It is also a good idea to avoid firing events whilst holding a lock, the reason
for this is that the class firing the event has little control over what locks t
he listener of the event will take in their event handler.
Put the test effort in
Deadlocks are annoying in the way they don't always kick in when running an appl
ication as they may depend on timing and race conditions. By making sure you tes
t (preferrably through unit testing) for all locking scenarios the offending cod

e can be identified early.


Having said that, this type of testing is usually more difficult that verifying
the result of something like Math.Abs and requires a bit of practice to get righ
t I think.
Keep an open mind
Deadlocks are creative and can strike where you least expect it, keep an open mi
nd and be vigilant when implementing concurrent systems that aquire exclusive lo
cks.
Eric Lippert has a great blog-post on The no-lock deadlock which highlights some
of the sneaky ways deadlocks can attack your code (sorry for not converting thi
s to VB.NET for you):
Hide Copy Code
class C
{
static C()
{
// Let's run the initialization on another thread!
var thread = new System.Threading.Thread(Initialize);
thread.Start();
thread.Join();
}
static void Initialize() { }
static void Main() { }
}

Вам также может понравиться