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
lockstatement.
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)