DEV Community

Cover image for Multithreading and Synchronization in C# πŸš€
Mo
Mo

Posted on

2

Multithreading and Synchronization in C# πŸš€

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}");
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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! πŸ‘‹

Heroku

Simplify your DevOps and maximize your time.

Since 2007, Heroku has been the go-to platform for developers as it monitors uptime, performance, and infrastructure concerns, allowing you to focus on writing code.

Learn More

Top comments (0)

Image of AssemblyAI

Automatic Speech Recognition with AssemblyAI

Experience near-human accuracy, low-latency performance, and advanced Speech AI capabilities with AssemblyAI's Speech-to-Text API. Sign up today and get $50 in API credit. No credit card required.

Try the API

πŸ‘‹ Kindness is contagious

Explore a sea of insights with this enlightening post, highly esteemed within the nurturing DEV Community. Coders of all stripes are invited to participate and contribute to our shared knowledge.

Expressing gratitude with a simple "thank you" can make a big impact. Leave your thanks in the comments!

On DEV, exchanging ideas smooths our way and strengthens our community bonds. Found this useful? A quick note of thanks to the author can mean a lot.

Okay