Open In App

C# Thread Synchronization

Last Updated : 20 Sep, 2025
Comments
Improve
Suggest changes
Like Article
Like
Report

In multithreaded applications, multiple threads often access shared data or resources simultaneously. This can cause race conditions, data inconsistency and unpredictable behavior. Thread synchronization in C# ensures that threads coordinate properly, preventing conflicts and maintaining correctness.

Why Thread Synchronization?

  • Avoids race conditions
  • Maintains thread safety
  • Ensures predictable program behavior

Synchronization ensures thread safety but may reduce performance if overused, as threads spend time waiting for locks to be released.

Synchronized Blocks in C#

In C#, synchronized blocks are written using the lock keyword on a specific object. Only one thread can enter the block at a time. Other threads are blocked until the lock is released.

Syntax:

lock(sync_object) {

// Access shared resources

}

Example:

C#
class Counter {
    private int c = 0; // Shared variable

    public void Inc() {
        lock (this) { // Synchronize only this block
            c++;
        }
    }

    public int Get() {
        return c;
    }
}

Synchronize Threads in C#

Threads can be synchronized in different ways depending on the requirement. C# provides multiple constructs to handle synchronization:

Using lock

The lock keyword is the most common way to synchronize threads. It restricts access to a code block so only one thread can execute it at a time.

C#
using System;
using System.Threading;

class Program
{
    private static int counter = 0;
    private static readonly object lockObj = new object();

    static void Increment()
    {
        for (int i = 0; i < 5; i++)
        {
            lock (lockObj)
            {
                counter++;
                Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} -> {counter}");
            }
            Thread.Sleep(100);
        }
    }

    static void Main()
    {
        Thread t1 = new Thread(Increment);
        Thread t2 = new Thread(Increment);

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

        t1.Join();
        t2.Join();
    }
}

Output
Thread 3 -> 1
Thread 4 -> 2
Thread 3 -> 3
Thread 4 -> 4
Thread 3 -> 5
Thread 4 -> 6
Thread 3 -> 7
Thread 4 -> 8
Thread 3 -> 9
Thread 4 -> 10

Explanation: Here, both threads attempt to increment counter. Without synchronization, values may overlap or skip. The lock ensures only one thread updates the counter at a time.

Using Monitor Class

The Monitor class provides a more flexible way of synchronizing threads. It works similarly to lock but offers additional control like Wait(), Pulse(), and PulseAll().

C#
class Demo
{
    private static int count = 0;
    private static readonly object sync = new object();

    static void Increment()
    {
        for (int i = 0; i < 3; i++)
        {
            Monitor.Enter(sync);
            try
            {
                count++;
                Console.WriteLine($"Count = {count}");
            }
            finally
            {
                Monitor.Exit(sync);
            }
        }
    }
}

Explanation: Monitor.Enter and Monitor.Exit provide explicit control. The try-finally ensures the lock is released even if an exception occurs.

Using Mutex

Mutex is used to synchronize threads across multiple processes. Unlike lock, it works not only within a single application but also across different applications.

C#
class Demo
{
    private static Mutex mutex = new Mutex();

    static void PrintNumbers()
    {
        mutex.WaitOne();
        for (int i = 1; i <= 3; i++)
        {
            Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} -> {i}");
            Thread.Sleep(200);
        }
        mutex.ReleaseMutex();
    }
}

Explanation: Here, WaitOne() acquires the mutex and ReleaseMutex() releases it. Only one thread across processes can hold the mutex at a time.

Using Semaphore

A Semaphore controls access to a resource by allowing a specified number of threads to enter at once. This is useful when you want to limit concurrent access.

C#
class Demo
{
    private static Semaphore semaphore = new Semaphore(2, 2);

    static void Work()
    {
        semaphore.WaitOne();
        Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} entered");
        Thread.Sleep(500);
        Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} exiting");
        semaphore.Release();
    }
}

Explanation: Here, a maximum of 2 threads can work simultaneously. Others wait until a slot is released.

Using SemaphoreSlim

SemaphoreSlim is a lightweight alternative to Semaphore and is recommended for synchronization within a single process.

C#
class Demo
{
    private static SemaphoreSlim sem = new SemaphoreSlim(2, 2);

    static async Task Work()
    {
        await sem.WaitAsync();
        Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} entered");
        await Task.Delay(500);
        Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} exiting");
        sem.Release();
    }
}

Explanation: It supports asynchronous programming and is more efficient when inter-process synchronization is not required.


Article Tags :

Explore