Hello everyone, Today, we're diving into some essential concepts in multithreading and synchronization in C#. We'll be discussing lock
, Mutex
, Semaphore
, and SemaphoreSlim
. These tools are crucial for managing concurrent access to resources in your applications. Without further ado, letβs get started! π
The lock
Statement π
The lock
keyword in C# ensures that a block of code runs by only one thread at a time. It's a simple way to prevent race conditions.
How lock
Works
At compile time, lock
is converted to Monitor
and is surrounded by a try-finally
block. In the finally
block, the thread is released even if there's an exception. So, lock
is essentially a simpler way of using Monitor
without writing try-finally
statements each time!
Hereβs an example to illustrate this:
object lockObject = new object();
int counter = 0;
int safeCounter = 0;
Parallel.For(0, 10000, i =>
{
counter++;
lock (lockObject)
{
safeCounter++;
}
});
Console.WriteLine($"Counter: {counter}");
Console.WriteLine($"Counter with lock: {safeCounter}");
In this example, we have a parallel loop iterating from 0 to 10000. It increases the values of counter
and safeCounter
, but safeCounter
is surrounded by the lock
statement.
In the first iteration, if three threads are running simultaneously and each reads the value of counter
(which is one), they will all increase the value to two. This means that the counter
will be two for all three threads, resulting in a race condition.
The lock
syntax prevents multiple threads from accessing safeCounter
simultaneously, ensuring that only one thread can access it at a time. This results in a sequential increase in value (e.g., one, two, three, four). As a result, we get the expected incremental value.
When we run the project, the value for the counter
is unexpectedly different due to race conditions, so it may vary on your computer. However, the value for the safeCounter
with the lock
is 10000.
Synchronizing External Processes π
Now, we might wonder what would happen if multiple external processes need to access a shared resource, considering we have internal threads in one process in this example.
Imagine having a shared resource like a file and multiple processes (e.g., Process One and Process Two). In this scenario, using lock
won't help because it's designed for internal threads within a process, not for synchronizing external processes.
Synchronization Solutions π οΈ
For better clarification, let's consider finding a synchronization solution in a multithreaded application with two categories:
Internal Process Scenarios
Single Thread:
- Use the
lock
statement.
Multiple Threads:
- Use
SemaphoreSlim
.
External Process Scenarios
Single Thread:
- Use a
Mutex
.
Multiple Threads:
- Use a
Semaphore
.
Examples for Mutex and Semaphore
Mutex Example
A Mutex
can be used to synchronize threads across different processes.
using Mutex mutex = new Mutex(false, "GlobalMutex");
if (!mutex.WaitOne(TimeSpan.FromSeconds(5), false))
{
Console.WriteLine("Another instance is running, exiting...");
return;
}
Console.WriteLine("No other instance is running, proceeding...");
Console.ReadLine();
Semaphore Example
A Semaphore
can be used to limit the number of threads that can access a resource simultaneously.
class Program
{
static Semaphore _semaphore = new Semaphore(2, 2);
static void Main()
{
for (int i = 0; i < 5; i++)
{
Thread t = new Thread(Worker);
t.Start();
}
}
static void Worker()
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} is waiting...");
_semaphore.WaitOne();
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} is working...");
Thread.Sleep(2000);
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} is releasing...");
_semaphore.Release();
}
}
Wrapping Up π¬
I hope you found this explanation of lock
, Mutex
, Semaphore
, and SemaphoreSlim
helpful. If you did, make sure to give this article a thumbs up π and subscribe to my YouTube channel for more programming tutorials. Thanks for reading, and Iβll see you in the next one! π
Top comments (0)