0% found this document useful (0 votes)
7 views

Synchronization

Uploaded by

jasonjacob
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views

Synchronization

Uploaded by

jasonjacob
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 8

Synchronization

14 October 2024 11:02

SYNCHRONIZATION
It is best to avoid synchronization issues by not sharing data between threads. access
If data sharing is necessary, you must use synchronization techniques so that only
one thread at a time accesses and changes shared state. Remember the
synchronization issues with race conditions and deadlocks.
This section discusses synchronization technologies that you can use with multiple
threads:
➤ lock statement
➤ Interlocked class
➤ Monitor class
➤ SpinLock struct
➤ WaitHandle class
➤ Mutex class
➤ Semaphore class
➤ Events classes
➤ Barrier class
➤ ReaderWriterLockSlim class
You can use the lock, Interlocked, and Monitor classes for synchronization within a
process.
The classes Mutex, Event, SemaphoreSlim, and ReaderWriterLockSlim also offer
synchronization among threads of multiple processes.

Lock;
The lock statement provides an easy way to hold and release a lock.

using System;
using System.Threading;
namespace LockDemo
{
class LockDisplay
{
public void DisplayNum()
{
// lock (this)
// {
for (int i = 1; i <= 5; i++)
{
Thread.Sleep(100);
Console.WriteLine("i = {0}", i);
}
// }
}
}
class Example
{
public static void Main(string[] args)
{
LockDisplay obj = new LockDisplay();

Console.WriteLine("Threading using Lock");

Thread t1 = new Thread(new ThreadStart(obj.DisplayNum));


Thread t2 = new Thread(new ThreadStart(obj.DisplayNum));
t1.Start();
t2.Start();
}
}
}

private object syncRoot = new object();

Interlocked;
i++ is not thread-safe.

It consists of getting a value from the memory, incrementing the value by 1, and
storing the value back in memory.
These operations can be interrupted by the thread scheduler.

C Sharp 2024 Page 1


These operations can be interrupted by the thread scheduler.
The Interlocked class provides methods for incrementing, decrementing,
exchanging, and reading values in a thread-safe manner.
Using the Interlocked class is much faster than other synchronization techniques.

public int State public int State


{ {
get get
{ {
lock (this) return Interlocked.Increment(ref state);
{ }
return ++state; }
}
}
}

Monitor:
The C# compiler resolves the lock statement to use the Monitor class.

lock (obj)
{
// synchronized region for obj
}
It invoke the Enter method, which waits until the thread gets the lock of the
object.
Only one thread at a time may be the owner of the object lock.
As soon as the lock is resolved, the thread can enter the synchronized section.
The Exit method of the Monitor class releases the lock.
The compiler puts the Exit method into a finally handler of a try block so that the
lock is also released if an exception is thrown.

Monitor.Enter(obj);
try
{
// synchronized region for obj
}
finally
{
Monitor.Exit(obj);
}

The Monitor class has a big advantage over the lock statement of C#:
you can add a timeout value for waiting to get the lock.
Therefore, instead of endlessly waiting to get the lock, you can use the TryEnter
method shown in the following example, passing a timeout value that defines the
maximum amount of time to wait for the lock.
If the lock for obj is acquired, TryEnter sets the Boolean ref parameter to true and
performs synchronized access to the state guarded by the object obj.
If obj is locked for more than 500 milliseconds by another thread, TryEnter sets the
variable lockTaken to false, and the thread does not wait any longer but is used to
do something else.
Maybe later, the thread can try to acquire the lock again.

bool lockTaken = false;


Monitor.TryEnter(obj, 500, ref lockTaken);
if (lockTaken)
{
try
{
// acquired the lock
// synchronized region for obj
}
finally
{
Monitor.Exit(obj);
}
}
else
{
// didn't get the lock, do something else
}

SpinLock:
Overhead of Monitor would be too high because of garbage collection, so SpinLock
struct is used

C Sharp 2024 Page 2


SpinLock:
Overhead of Monitor would be too high because of garbage collection, so SpinLock
struct is used
SpinLock is useful if you have a large number of locks and hold times are always
extremely short.
SpinLock is very similar in usage to the Monitor class.
You acquiring the lock with Enter or TryEnter, and release the lock with Exit.
SpinLock also offers two properties to provide information about whether it is
currently locked: IsHeld and IsHeldByCurrentThread.

WaitHandle
WaitHandle is an abstract base class that you can use to wait for a signal to be set.
The method BeginInvoke of the asynchronous delegate returns an object that
implements the interface IAsyncResult.
Using IAsyncResult, you can access a WaitHandle with the property
AsyncWaitHandle.
When you invoke the method WaitOne, the thread waits until a signal is received
that is associated with the wait handle.

static void Main()


{
TakesAWhileDelegate d1 = TakesAWhile;
IAsyncResult ar = d1.BeginInvoke(1, 3000, null, null);
while (true)
{
Console.Write(".");
if (ar.AsyncWaitHandle.WaitOne(50, false))
{
Console.WriteLine("Can get the result now");
break;
}
}
int result = d1.EndInvoke(ar);
Console.WriteLine("result: {0}", result);
}

With WaitHandle, you can wait for one signal to occur (WaitOne), multiple objects
that all must be signaled (WaitAll), or one of multiple objects (WaitAny).
WaitAll and WaitAny are static members of the WaitHandle class and accept an
array of WaitHandle parameters.

The classes Mutex, EventWaitHandle, and Semaphore are derived from the base
class WaitHandle.

Mutex:
Mutex (mutual exclusion) is one of the classes of the .NET Framework that offers
synchronization across multiple processes.
It is very similar to the Monitor class in that there is just one owner.
That is, only one thread can get a lock on the mutex and access the synchronized
code regions that are secured by the mutex.
With the constructor of the Mutex class, you can define whether the mutex should
initially be owned by the calling thread, define a name for the mutex, and
determine whether the mutex already exists.
In the following example, the third parameter is defined as an out parameter to
receive a Boolean value if the mutex was newly created.
If the value returned is false, the mutex was already defined.
The mutex might be defined in a different process, because a mutex with a name is
known to the operating system and is shared among different processes.
If no name is assigned to the mutex, the mutex is unnamed and not shared among
different processes.

bool createdNew;
Mutex mutex = new Mutex(false, "ProCSharpMutex", out createdNew);

To open an existing mutex, you can also use the method Mutex.OpenExisting,
which doesn’t require the same .NET privileges as creating the mutex with the
constructor.
Because the Mutex class derives from the base class WaitHandle, you can do a
WaitOne to acquire the mutex lock and be the owner of the mutex during that
time.
The mutex is released by invoking the ReleaseMutex method:
if (mutex.WaitOne())
{
try
{

C Sharp 2024 Page 3


{
// synchronized region
}
finally
{
mutex.ReleaseMutex();
}
}
else
{
// some problem happened while waiting
}

using System;
using System.Threading;
namespace MutexDemo
{
class Program
{
private static Mutex mutex = new Mutex();
static void Main(string[] args)
{
//Create multiple threads to understand Mutex
for (int i = 1; i <= 5; i++)
{
Thread threadObject = new Thread(MutexDemo);
threadObject.Name = "Thread " + i;
threadObject.Start();
}
Console.ReadKey();
}
//Method to implement syncronization using Mutex
static void MutexDemo()
{
Console.WriteLine(Thread.CurrentThread.Name + " Wants to Enter Critical
Section for processing");
try
{
//Blocks the current thread until the current WaitOne method receives a
signal.
//Wait until it is safe to enter.
mutex.WaitOne();
Console.WriteLine("Success: " + Thread.CurrentThread.Name + " is
Processing now");
Thread.Sleep(2000);
Console.WriteLine("Exit: " + Thread.CurrentThread.Name + " is
Completed its task");
}
finally
{
//Call the ReleaseMutex method to unblock so that other threads
//that are trying to gain ownership of the mutex.
mutex.ReleaseMutex();
}
}
}
}

Semaphore
A semaphore is very similar to a mutex; but unlike the mutex, the semaphore can
be used by multiple threads at once.
A semaphore is a counting mutex, meaning that with a semaphore you can define
the number of threads that are allowed to access the resource guarded by the
semaphore simultaneously.
This is useful if you need to limit the number of threads that can access the
resources available.
For example, if a system has three physical I/O ports available, three threads can
access them simultaneously, but a fourth thread needs to wait until the resource is
released by one of the other threads.
.NET 4.5 provides two classes with semaphore functionality: Semaphore and
SemaphoreSlim.
Semaphore can be named, can use system-wide resources, and allows
synchronization between different processes.
SemaphoreSlim is a lightweight version that is optimized for shorter wait times.
In the following example application, in the Main method six tasks are created and

C Sharp 2024 Page 4


In the following example application, in the Main method six tasks are created and
one semaphore with a count of 3.
In the constructor of the Semaphore class, you can define the count for the
number of locks that can be acquired with the semaphore (the second parameter)
and the number of locks that are free initially (the first parameter).
If the first parameter has a lower value than the second parameter, the difference
between the values defines the already allocated semaphore count.

using System;
using System.Threading;
using System.Threading.Tasks;
namespace Wrox.ProCSharp.Threading
{
class Program
{
static void Main()
{
int taskCount = 6;
int semaphoreCount = 3;
var semaphore = new SemaphoreSlim(semaphoreCount, semaphoreCount);
var tasks = new Task[taskCount];
for (int i = 0; i < taskCount; i++)
{
tasks[i] = Task.Run(() => TaskMain(semaphore));
}
Task.WaitAll(tasks);
Console.WriteLine("All tasks finished");
}

static void TaskMain(SemaphoreSlim semaphore)


{
bool isCompleted = false;
while (!isCompleted)
{
if (semaphore.Wait(600))
{
try
{
Console.WriteLine("Task {0} locks the semaphore",
Task.CurrentId);
Thread.Sleep(2000);
}
finally
{
Console.WriteLine("Task {0} releases the semaphore",
Task.CurrentId);
semaphore.Release();
isCompleted = true;
}
}
else
{
Console.WriteLine("Timeout for task {0}; wait again",
Task.CurrentId);
}
}
}
}
}

Events
Like mutex and semaphore objects, events are also system-wide synchronization
resources.
The .NET Framework offers the classes ManualResetEvent, AutoResetEvent,
ManualResetEventSlim, and CountdownEvent in the namespace
System.Threading.

You can use events to inform other tasks that some data is present, that
something is completed, and so on.
An event can be signaled or not signaled.
A task can wait for the event to be in a signaled state with the help of the
WaitHandle class.

A ManualResetEventSlim is signaled by invoking the Set method, and returned to a


nonsignaled state with the Reset method.
If multiple threads are waiting for an event to be signaled and the Set method is

C Sharp 2024 Page 5


If multiple threads are waiting for an event to be signaled and the Set method is
invoked, then all threads waiting are released.
In addition, if a thread just invokes the WaitOne method but the event is already
signaled, the waiting thread can continue immediately.

An AutoResetEvent is also signaled by invoking the Set method; and you can set it
back to a nonsignaled state with the Reset method. However, if a thread is waiting
for an auto-reset event to be signaled, the event is automatically changed into a
nonsignaled state when the wait state of the first thread is finished.

This way, if multiple threads are waiting for the event to be set, only one thread is
released from its wait state.
It is not the thread that has been waiting the longest for the event to be signaled,
but the thread waiting with the highest priority.

using System;
using System.Threading;
class Program
{
static ManualResetEventSlim eventManualWorker = new
ManualResetEventSlim(false);
static void Main(string[] args)
{
Thread t1 = new Thread(() => ManualResetFun(3000));
Thread t2 = new Thread(() => ManualResetFun(5000));
Thread t3 = new Thread(() => ManualResetFun(8000));

t1.Name = "thread1";
t2.Name = "thread2";
t3.Name = "thread3";

t1.Start();
t2.Start();
t3.Start();

eventManualWorker.Set();
Console.WriteLine("Main thread stopped for 6000 miliseconds");
Thread.Sleep(6000);
eventManualWorker.Reset();
Console.WriteLine("Main thread stopped for 4000 miliseconds");
Thread.Sleep(4000);
eventManualWorker.Set();
Console.ReadLine();
}

static void ManualResetFun(int seconds)


{
Console.WriteLine("Printing started by {0}", Thread.CurrentThread.Name);
Console.WriteLine("{0} stopped for {1} miliseconds",
Thread.CurrentThread.Name, seconds);
Thread.Sleep(seconds);
eventManualWorker.Wait();
Console.WriteLine("Printing completed by {0}", Thread.CurrentThread.Name);
}
}

Barrier
For synchronization, the Barrier class is great for scenarios in which work is forked
into multiple tasks and the work must be joined afterward.
Barrier is used for participants that need to be synchronized.
While the job is active, additional participants can be added dynamically — for
example, child tasks that are created from a parent task.
Participants can wait until the work is done by all the other participants before
continuing.

using System;
using System.Threading;
class Program
{
static Barrier _barrier = new Barrier(3); //how many threads participating

static void Main()


{
new Thread(Speak).Start();
new Thread(Speak).Start();
new Thread(Speak).Start();

C Sharp 2024 Page 6


{
new Thread(Speak).Start();
new Thread(Speak).Start();
new Thread(Speak).Start();
}

static void Speak()


{
for (int i = 0; i < 5; i++)
{
Console.Write(i + " ");
Thread.Sleep(1000);
_barrier.SignalAndWait();
}
}
}

ReaderWriterLockSlim:
In order for a locking mechanism to allow multiple readers, but only one writer, for
a resource, the class ReaderWriterLockSlim can be used.
This class offers a locking functionality whereby multiple readers can access the
resource if no writer locked it, and only a single writer can lock the resource.
The ReaderWriterLockSlim class has properties to acquire a read lock that are
blocking and nonblocking, such as EnterReadLock and TryEnterReadLock, and to
acquire a write lock with EnterWriteLock and TryEnterWriteLock.
If a task reads first and writes afterward, it can acquire an upgradable read lock
with EnterUpgradableReadLock or TryEnterUpgradableReadLock.
With this lock, the write lock can be acquired without releasing the read lock.
Several properties of this class offer information about the held locks, such as
CurrentReadCount, WaitingReadCount, WaitingUpgradableReadCount, and
WaitingWriteCount.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Wrox.ProCSharp.Threading
{
class Program
{
private static List<int> items = new List<int>() { 0, 1, 2, 3, 4, 5};
private static ReaderWriterLockSlim rwl = new
ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
static void ReaderMethod(object reader)
{
try
{
rwl.EnterReadLock();
for (int i = 0; i < items.Count; i++)
{
Console.WriteLine("reader {0}, loop: {1}, item: {2}", reader, i,
items[i]);
Thread.Sleep(40);
}
}
finally
{
rwl.ExitReadLock();
}
}
static void WriterMethod(object writer)
{
try
{
while (!rwl.TryEnterWriteLock(50))
{
Console.WriteLine("Writer {0} waiting for the write lock", writer);
Console.WriteLine("current reader count: {0}",
rwl.CurrentReadCount);
}
Console.WriteLine("Writer {0} acquired the lock", writer);
for (int i = 0; i < items.Count; i++)
{
items[i]++;
Thread.Sleep(50);
}

C Sharp 2024 Page 7


}
Console.WriteLine("Writer {0} finished", writer);
}
finally
{
rwl.ExitWriteLock();
}
}
static void Main()
{
var taskFactory = new TaskFactory(TaskCreationOptions.LongRunning,
TaskContinuationOptions.None);
var tasks = new Task[6];
tasks[0] = taskFactory.StartNew(WriterMethod, 1);
tasks[1] = taskFactory.StartNew(ReaderMethod, 1);
tasks[2] = taskFactory.StartNew(ReaderMethod, 2);
tasks[3] = taskFactory.StartNew(WriterMethod, 2);
tasks[4] = taskFactory.StartNew(ReaderMethod, 3);
tasks[5] = taskFactory.StartNew(ReaderMethod, 4);
for (int i = 0; i < 6; i++)
{
tasks[i].Wait();
}
}
}
}

C Sharp 2024 Page 8

You might also like