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

UNIVERSITY OF PRISHTINA

FACULTY OF ELECTRICAL AND COMPUTER ENGINEERING


DEPARTMENT OF COMPUTER ENGINEERING

Seminar work
Synchronization possibilities and features in C#

Professor:
Dr. tech. Blerim Rexha

Students:
Valdrin Haxhiu
Vehbi Neziri

January 2010, Prishtin

Abstract
In this seminar work discussed one of the greatest features of the general purpose computer programming
language - C#. Multitasking, which means doing many things at the same time (of course, this is
something relative, because the processor executes given tasks in parallel so it makes us think that they
are executing simultaneously) is one of the most fundamental concepts in computer engineering and
computer science. Multitasking is related to other fundamental concepts like processes and threads. A
process is a computer program that is executing in a processor, while a thread is a part of a process that
has a way of execution; it is a thread of execution. Every process has at least one thread of execution.
There are two types of multitasking: process-based and thread-based. Process-based multitasking, means
that on a given computer there can be more than one program or process that is executing, while threadbased multitasking, which is also known as multithreading, means that within a process, there can be
more than one thread of execution, each of them doing a job and so accomplishing the job of their
process. When there are many processes or many threads within processes, they may have to cooperate
with each other or concurrently try to get access to some shared computer resources like: processor,
memory and input/output devices. They may have to, for example: print a file in a printer or write and/or
read to the same file. We needed to avoid situations where some processes and/or threads might make
things go in an unpredictable way. We need a way of setting an order, where processes and/or threads
could do their jobs (user jobs) without any problem, we need to synchronize them. C# has built-in support
for process and thread synchronization, there are some constructs that can be used when synchronization
is needed, and most of them are covered in this work.

1. INTRODUCTION
In the abstract of this seminar work, we mentioned the problem of synchronizing processes and threads.
In the following sections we have covered in more detail the problem of synchronization, starting with a
real world example problem of synchronization, with the problem of withdrawing the same amount of
money from the same bank account, two different ATMs (Automated Teller Machine) and at the same
time from two persons (there can be even more persons, making things worse). Then, we have discussed
some of the strategies that we could use to solve the problem of synchronization in the problem
mentioned before. We have started with a basic concept of synchronization, the concept of a lock,
discussing its main features. The concept of a lock, in a more extended form, is used in many other
synchronization strategies in C#. We have covered the Monitor, Mutex, Semaphore and Events as built-in
constructs supported for process and thread synchronization. In this seminar work, we have been focused
on thread synchronization strategies.

2. SYNCHRONIZATION
2.1 Why do we need synchronization?
To understand more deeply the problem of synchronization of threads in multithreaded applications,
in this section, we have mentioned a possible problem of synchronization (of absence of synchronization).
The situation could be like this: a couple, Mr. X and Mrs. X, are going to withdraw 1,000 from the same
bank account, at the same time from two different ATMs. This situation is depicted in figure 1.

Figure 1. Money withdraw from the same bank account at the same time

For example: Mr. X gets access to the method Withdraw( ) (that is part of the bank software) shown in
figure 1, he will withdraw 1,000 (the least amount of money left in the account) from the account. In
this case there no means of synchronization or a way of preventing other persons (of course those that
might have credentials to use this account) from gaining access to the same account at the same time, so
Mrs. X could get access to the method Withdraw ( ) at the same time while Mr. X was doing the
withdraw. Mrs. X would get the wrong information that there are still 1,000 left in the account and
would order a transaction to withdraw them, that would cause the account to be overdrawn (well, that
would depend on the bank policies and the properties of the account). This situation would be a great
problem. We need synchronization, because we do not want to have problems when there are resources
that might be used by many processes and/or threads of execution. The example that we mentioned before
or the problem that it posed, could be solved be means of synchronization, synchronizing threads (user
threads) that might want to get access to the same resource at the same time, to the same bank account,
this is called making it thread safe. Thread safety could be achieved by at least three ways: synchronizing
critical sections within code (software code), making critical objects immutable and using a thread safe
wrapper. A critical section is a piece of code in the program that may be accessed by multiple threads at
the same time to update the state of the object [TFTS04]. According to the first way, applied to our
example problem, we could synchronize the method Withdraw () so that if one of our persons would
access this method and withdraw the amount of money, the other person would not be allowed to access
that method in the same time, it could be possible only when the one that had access to it, would release
it, so that the ATM would tell the other that there were no money left in the account. The second method
deals with immutable objects. An alternative way to make an object thread-safe is to make the object
immutable. An immutable object is one whose state can't be changed once the object has been created.
This can be achieved by not allowing any thread to modify the state of the Account object once it is
created. In this approach, we separate out the critical sections that read the instance variables from those
that write to instance variables. The critical sections that only read the instance variables are left as they
are, whereas the critical sections that change the instance variables of the object are changed so that,
instead of changing the state of the current object, a new object is created that embodies the new state,
and a reference to that new object is returned. In this approach, we do not need to lock the critical section
because no methods of an immutable object actually writes to the object's instance variables, which
means, an immutable object is by definition thread-safe. The third way of making an object thread-safe is
to write a wrapper class over the object that will be thread-safe rather than making the object itself threadsafe. The object will remain unchanged and the new wrapper class will contain synchronized sections of
thread-safe code.

2.2 Synchronization with the Lock statement


One of the useful constructs for synchronization in C# is the lock statement. The key to
synchronization is the concept of a lock, which controls access to a block of code within an object. When
an object is locked by one thread, no other thread can gain access to the locked block of code [H09]. After
a thread which has been using a locked object releases the lock, other threads can gain access (of course,
only one of them at any time) to the block of code or the critical section that was synchronized with the
lock. In the listing 1, we have shown a short fragment (just to have the idea) of code that might be
synchronized with the lock statement.

lock (lockObj)
{
// enter critical section
// statements to be synchronized
// exit critical section
}

Listing 1. A fragment of code that could be synchronized with the lock

In listing 1, lockObj is a reference to the object being synchronized. A lock statement ensures that the
section of code protected by the lock for the given object can be used only by the thread that obtains the
lock. All other threads are blocked until the lock is removed. The lock is removed when the block is
exited. The object we lock on is an object that represents the resource being synchronized. A key point to
understand about lock is that the lock-on object should not be publically accessible. Why? Because it is
possible that another piece of code that is outside your control could lock on the object and never release
it [H09]. We have to be very careful when we do synchronization in C# using the lock statement.

2.3 Synchronization using the Monitor and the Mutex constructs


Another possibility of synchronizing pieces of source code in C# is by using the classes: Monitor and
Mutex. These two classes are within the namespace System.Threading, which must be included in every
written C# program that creates threads. Synchronization can be achieved by using some predefined
methods that are some of the members of the two classes mentioned before.
Synchronization with the Monitor class is similar to synchronization with the lock statement, except that
the Monitor class offers more possibilities. When a thread wishes to acquire exclusive control over an
object, the thread invokes Monitor method Enter to acquire the lock on that data object [HP05]. Each
object has a SyncBlock that maintains the state of that object's lock. Methods of class Monitor use the

data in an object's SyncBlock to determine the state of the lock for that object. After acquiring the lock for
an object, a thread can manipulate that object's data. While the object is locked, all other threads
attempting to acquire the lock on that object are blocked from acquiring the lock; such threads enter the
Blocked state. When the thread that locked the shared object no longer requires the lock, that thread
invokes Monitor method Exit to release the lock [HP05]. This updates the SyncBlock of the shared object
to indicate that the lock for the object is available again. At this point, if there is a thread that was
previously blocked from acquiring the lock on the shared object, that thread acquires the lock to begin its
processing of the object. The illustration in figure 2 illustrates (simply) the Monitor functionality.

Figure 2. Monitor functionality

In the listing 2, we have shown a short fragment (just to have the idea) of code that might be
synchronized with the Monitor class.

public void criticalsection()

{
// enter critical section

Monitor.Enter(this);
// statements to be synchronized
// exit critical section
Monitor.Exit(this);
}

Listing 2. A fragment of code that could be synchronized with the Monitor

Another useful class for synchronization is the Mutex class. We may use the methods WaitOne ( ) and
ReleaseMutex ( ) defined in the Mutex class when we want to synchronize a shared piece of source code
in C#. First we have to define an object of the class Mutex so that our threads can gain access (only one at
a time) to the synchronized code by calling the method WaitOne ( ). When the thread that gained access
has finished its job, it must call the method ReleaseMutex ( ) to exit the synchronized code and make
possible for another thread to access that code. In the listing 3, we have shown a short fragment (just to
have the idea) of code that might be synchronized with the Mutex class.
Mutex myMutex = new Mutex();

public void criticalsection()


{
// enter critical section
myMutex.WaitOne();
// statements to be synchronized
// exit critical section
myMutex.ReleaseMutex();
}
Listing 3. A fragment of code that could be synchronized with the Mutex

2.4 Synchronization using the Semaphore and the Event constructs


Synchronization in C# offers the opportunity to grant more than one thread access to a shared
resource at the same time. That can be achieved be using the Semaphore class.
A semaphore controls access to a shared resource through the use of a counter. If the counter is greater
than zero, then access is allowed. If it is zero, access is denied. What the counter is counting are permits.
Thus, to access the resource, a thread must be granted a permit from the semaphore.
In general, to use a semaphore, the thread that wants access to the shared resource tries to acquire a
permit. If the semaphores counter is greater than zero, the thread acquires a permit, which causes the
semaphores count to be decremented. Otherwise, the thread will block until a permit can be acquired.
When the thread no longer needs access to the shared resource, it releases the permit, which causes the
semaphores count to be incremented. If there is another thread waiting for a permit, then that thread will
acquire a permit at that time. The number of simultaneous accesses permitted is specified when the
semaphore is created. If you create a semaphore that allows only one access, then a semaphore acts just
like a mutex. Semaphores are especially useful in situations in which a shared resource consists of a group
or pool. For example, a collection of network connections, any of which can be used for communication,
is a resource pool [H09]. A thread needing a network connection does not care which one it gets. The
semaphore class has several constructors, one of them is:
public Semaphore(int initial, int max)

Where initial specifies the initial value of the semaphore permit counter, which is the number of permits
available. The maximum value of the counter is passed in max. Thus, max represents the maximum
number of permits that can granted by the semaphore. The value in initial specifies how many of these
permits are initially available. Using a semaphore is similar to using a mutex. To acquire access, our code
will call WaitOne ( ) on the semaphore. WaitOne ( ) waits until the semaphore on which it is called can be
acquired. Thus, it blocks execution of the calling thread until the specified semaphore can grant
permission. When our code no longer needs ownership of the semaphore, it releases it by calling Release(
). In the listing 4, we have shown a short fragment (just to have the idea) of code that might be
synchronized with the Semaphore class.

Semaphore sem = new Semaphore(2, 2);

public void criticalsection()


{
// enter critical section
sem.WaitOne();
// statements to be synchronized
// exit critical section
sem.Release();
}

Listing 4. A fragment of code that could be synchronized with the Semaphore

In many programs there can be situations in which one thread is waiting for some event to occur in
another thread. C# supports another type of synchronization object: the event. There are two types of
events: manual reset and auto reset. These are supported by the classes ManualResetEvent and
AutoResetEvent. When the event takes place, the second thread signals the first, allowing it to resume
execution.
The constructors for ManualResetEvent and AutoResetEvent are shown here:
public ManualResetEvent(bool status)
public AutoResetEvent(bool status)

Here, if status is true, the event is initially signaled. If status is false, the event is initially non-signaled.

Events are easy to use. For a ManualResetEvent, the procedure works like this. A thread that is waiting
for some event simply calls WaitOne( ) on the event object representing that event.WaitOne( ) returns
immediately if the event object is in a signaled state. Otherwise, it suspends execution of the calling
thread until the event is signaled. After another thread performs the event, that thread sets the event object
to a signaled state by calling Set ( ). Thus, a call to Set ( ) can be understood as signaling that an event has
occurred. After the event object is set to a signaled state, the call to WaitOne( ) will return and the first
thread will resume execution. The event is returned to a non-signaled state by calling Reset ( ).
The difference between AutoResetEvent and ManualResetEvent is how the event gets reset. For
ManualResetEvent, the event remains signaled until a call to Reset ( ) is made. For AutoResetEvent, the
event automatically changes to a non-signaled state as soon as a thread waiting on that event receives the
event notification and resumes execution.

3. CONLUSION
At the end of this seminar work, we have concluded that when it comes to process and thread
synchronization, C# offers many possibilities. Its built in constructs for synchronization are not difficult
to understand and use in many synchronization scenarios (although we have covered them in general),
from those that require allowing just one thread of execution to use protected shared resources at a time to
those that may allow a number of threads use protected shared collections of a computer resource.

References
[TFTS04]

Tobin Titus, Fabio Claudio Ferracchiati, Tejaswi Redkar, Srinivasa Sivakumar, C#


Threading Handbook, Apress, 2004

[H09]

Herbert Schildt, C# 3.0 - The Complete Reference, Mc Graw Hill Companies, 2009

[HP05]

Harvey. M. Deitel, Paul. J. Deitel, Visual C# 2005: How to Program, Second


Edition, Prentice Hall, 2005

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