|
Synchronisation
plays an important role in multithreaded applications where
the threads are not independent of each other and share common
resources. This is because if two threads share common resources
and try to manipulate the resources simultaneously, the resources
become inconsistent. To tackle such situations synchronisation
provides a lock on the resource that is shared among threads
and makes a thread wait until the other thread finishes the
job with the resource. Thus synchronisation locks
the shared resource and prevents another thread from using
it.
C# provides a lock keyword to lock the shared object or resource.
Whatever is written inside the parenthesis following the lock
keyword gets locked for the current thread and no other thread
is permitted to obtain the lock on that resource or object.
We can write an expression that evaluates to an object, or
just the object on which we wish to have a lock, inside the
parenthesis. Whatever the expression may be, the result should
be a reference type. The code that uses the object to be synchronised
should be written inside the block of the lock statement.
To give you a more concrete example, consider two threads
working on a file. If both the threads try to open the file
and write to it simultaneously, an exception is thrown. Here
what we need to do is synchronise both the threads in such
a way that only one thread opens the file and writes in it
at any given moment.
We have illustrated the same scenario in the example that
follows. The User Interface of the application is shown in
the following screen shot:

Here we have added a textbox named quantity, a button called
purchase and a label called msg. In this application we have
simply asked the user the number of goods he wants to purchase,
i.e. the demand. We have maintained number of goods in stock
in a file called log.bin. Whenever the user purchases a certain
amount of goods, the number in the file is updated.
In this application we have also launched another thread which
keeps on checking continuously whether the goods are out of
stock or not. If the thread finds that the number of goods
in the store is zero, it fills the store with hundred items
and hence updates the number in log.bin file.
We need to synchronise the two threads so that only one thread
works on log.bin at one time. We added the following as data
members of the Form1 class.
FileInfo f;
FileStream fs;
byte total=100;
total denotes the total number of goods in store. We have
used a value like 100 that can be represented in one byte.
This is because FileStream is used to write an array of bytes
in the file and if we take a larger number, we would have
to first create an array of bytes representing the large number.
For the sake of simplicity here we have used a number that
can be represented in one byte.
We initialised the reference f, and launched the thread that
would keep on checking the stock from time to time after the
call to the InitializeComponent( ) method in the constructor
as shown below:
f = new FileInfo ( C:\\log.bin );
Thread t = new Thread (new ThreadStart(checker));
t.IsBackground = true;
t.Start();
We have made this thread a background thread because we wish
that as soon as the application terminates, the thread should
also get terminated. The checker( ) method is given below:
public void checker()
{
while ( true )
{
lock (f)
{
try
{
fs = f.OpenWrite( ) ;
if ( total <= 0 )
{
total = ( byte ) ( total + 100 ) ;
fs.WriteByte ( total ) ;
msg.Text = Stock sold out!!!! Goods incremented ;
}
else
msg.Text = Items in stock: + total ;
Thread.Sleep ( 1000 ) ;
fs.Close();
}
catch
{
MessageBox.Show ( Can not Open file ) ;
}
}
}
}
We have put the checker logic in a while loop so that the
store is checked continuously after every 1000 milliseconds
(Thread.Sleep(1000) ensures this). Next we have obtained a
lock on the file object referred to by f using the lock statement.
After acquiring the lock we have checked whether total is
zero or less than zero. If yes, we have incremented total
by 100 and written the value back in the file. If it is not
zero or less than zero, we have simply displayed the number
of goods in the label named msg. We have put the whole logic
in a try-catch block, the reason for which would be clarified
later.
Now if the user supplies some number in the textbox and clicks
on the purchase button, the following handler gets called:
private void purchase_Click ( object sender, System.EventArgs e )
{
lock ( f )
{
try
{
fs = f.OpenWrite( ) ;
byte b = byte.Parse ( quantity.Text ) ;
total = ( byte ) ( total - b ) ;
fs.WriteByte ( total ) ;
MessageBox.Show ( Transaction Complete + \n + Item
remaining: + total ) ;
fs.Close( ) ;
}
catch
{
MessageBox.Show ( Cannot Open file ) ;
}
}
}
Here also we have first acquired a lock on the file object.
Next we have collected the quantity demanded by the user in
a byte called b. Then we have subtracted that amount from
total and written back the new value of total in the file.
We have put this logic also in a try-catch block. This method
gets executed in the main thread, whereas checker( ) gets
executed in a thread referred to by t.
Now if you execute the program, you would see that the number
of goods in the store is displayed continuously on the msg
label. As soon as the user purchases some goods, the number
is updated and when the number goes below zero, the goods
are incremented.
This works fine, but if you really want to understand the
need of synchronisation, remove or comment the lock statement
and see the effect. As soon as the user clicks on purchase,
an exception gets thrown indicating that the file is already
in use. To catch this exception we have used the try-catch
blocks. The exception gets thrown because both the threads
try to open and write to the file simultaneously.
Besides the lock statement, synchronisation can also be achieved
in C# using the Interlocked class, the Monitor class, Mutexes
and Events.
The Interlocked class provides four methods to perform operations
like incrementing, decrementing or exchanging variables in
a thread-safe manner. The four static methods areIncrement(
), Decrement( ), Exchange( ), and CompareExchange().
Monitors: In .NET, Monitors are similar to the lock statement
and are used to synchronise concurrent thread accesses so
that any resource can be manipulated only by one thread at
a time. Thus they support mutually exclusive access to a shared
resource. Monitors are similar to Critical Sections in Windows.
The Framework Class library provides a Monitor class to represent
the Monitor. The above program will also work if we use Monitor.Enter(
f ) and Monitor.Exit( f ) methods, belonging to the Monitor
class, instead of the lock statement. All we need to do is
to write the code present in the lock block between the calls
to Monitor.Enter( f ) and Monitor.Exit( f ) methods. In fact
when we use the lock statement it gets resolved into calls
to Monitor.Enter( ) and Monitor.Exit( ) methods in the IL
code.
Mutexes: The word mutex comes from two words-mutually
and exclusive. Mutexes are similar to monitors
except that they permit the object or resource to be shared
across processes. Mutexes can thus synchronise threads belonging
to different applications. The System.Threading.Mutex class
represents a mutex object.
Events: are used to synchronise threads in some order. For
example if two threads are executing and we want that not
only should the threads work synchronously,
but they should also get executed alternately, we should use
events. If we use events we will get the following order of
execution:
Thread1 executing
Thread2 executing
Thread1 executing
Thread2 executing
And so on... But instead if we use only monitors, we will
achieve synchronisation but threads are likely to get executed
in the following manner:
Thread1 executing
Thread1 executing
Thread2 executing
Thread1 executing
The .NET Framework class library wraps two of the Windows
kernel objects-auto-reset events and manual-reset events-into
two classes called AutoResetEvent and ManualResetEvent.
 |
Yashavant
Kanetkar, one of the first Express Computer columnists,
is an established software expert, speaker and author
with several best-sellers to his credit, including titles
like “Let Us C” and the “Fundas” series. Contact him at
kanet@nagpur.dot.net.in |
|