Being threadsafe - an introduction to the pitfalls of parallelism
Written by Mike James   
Tuesday, 06 July 2010
Article Index
Being threadsafe - an introduction to the pitfalls of parallelism
Making a thread safe
Concurrent exectution and exclusion
Locking in practice
Deadlock

 

Locking in practice

There is a great deal of custom and practice concerning which objects should be used.

For example, it is often said that you should lock on this for instance methods and a type object for static methods.

The logic is that each instance method will only access resources that belong to that instance and so thread locking only has to be specific to that instance. However, a static method is common to all instances and hence likely to need locking so that one thread can only access it at a time irrespective of which instance it is called from.

These conventions have some sense but an equally good, and some might argue better, approach is to create and use objects specifically to be used to lock a resource.

Let’s see how this works.

First we need an object to use as a lock:

static readonly object
MyCountLock=new object();

As we don’t want multiple copies of the lock it should be static and as we don’t want anyone to change it – readonly.

To make use of it we have to modify the count method quoted earlier to read:

public void A()
{
for (int i = 0; i < 99; i++)
{
Monitor.Enter(MyCountLock);
int temp=count+1;
Thread.Sleep(2);
if (temp != count + 1)
{
return;
}
count = temp;
Monitor.Exit(MyCountLock);
}
}

The calls to the static Monitor object acquire and release the lock using our object.

To try this out we need a slightly different main program that runs two copies of method A:

count = 0;
Thread T1 = new Thread(A);
Thread T2 = new Thread(A);

T1.Start();
T2.Start();

T1.Join();
T2.Join();
textBox1.Text += count.ToString();

If you place a breakpoint on the return you will see that the update is performed in an orderly fashion in the sense that neither thread interrupts the other during the update.

If you comment out the calls to the monitor then you will immediately see that the two threads do interfere with one another.

If you don’t want to use a specially created object then you can use the more commonly encountered lock on this:

Monitor.Enter(this);
int temp=count+1;
Thread.Sleep(2);
if (temp != count + 1)
{
return;
}
count = temp;
Monitor.Exit(this);

The lock using the current instance works equally well but imagine what would happen if the monitor was protecting a resource shared by multiple instances of the class.

The result would be messy to say the least as each instance would obey the lock but threads from different instances would access it at the same time.

Similarly a lock on this isn’t very useful for controlling access from different objects. Another problem with locking on the current instance is that you might well forget that it is being used to protect a particular resource and accidentally use it to protect another completely unconnected resource. This would result in a thread accessing resource one, unnecessarily blocking all access to resource two.

In many ways it is better to create an object specifically to be used to lock a particular resource and include the name of the resource in the name of the lock.

Don’t make the common mistake of using a string or a value “object” to lock because there are pitfalls in using both. The string might well end up being shared due to optimisation and the value “object” would be boxed and unboxed each time it was used -making the lock useless.

Another potential problem with using the Monitor in the way described is that if the code crashes while it has a lock then the lock never gets released.

Similarly you could accidentally forget to release the lock or attempt to release the lock on the wrong object. You can avoid the problem of a crash by wrapping the code in a Try-Catch clause but it’s much easier to use the equivalent lock statement (synclock in VB). That is:

lock(object){list of instructions}

is equivalent to:

try{
Monitor.Enter(object);
  list of instructions

}
finally{Monitor.Exit(object);

In other words, lock will try to obtain a lock using the specified object before executing the list of instructions within a try.

No matter what happens you can be sure that the lock will be released so that other threads can use the resource.

<ASIN:1449380344>

<ASIN:0123745144>

<ASIN:0321658701>

<ASIN:0321741765>

<ASIN:0470495995>

<ASIN:0672330792>

<ASIN:0521114292>

<ASIN:0596521537>

 



Last Updated ( Tuesday, 06 July 2010 )