ConcurrencyC#verifiedVerified
Mutex / Lock Pattern in C#
Guarantee that only one thread at a time can access a shared resource by requiring threads to acquire an exclusive lock before proceeding.
How to Implement the Mutex / Lock Pattern in C#
1Step 1: Shared resource protected by a lock
public class BankAccount
{
private readonly object _lock = new();
private decimal _balance;
public BankAccount(decimal initial) => _balance = initial;
public decimal Balance
{
get { lock (_lock) { return _balance; } }
}2Step 2: Mutex ensures only one thread modifies balance at a time
public bool Withdraw(decimal amount)
{
lock (_lock)
{
if (_balance < amount) return false;
_balance -= amount;
return true;
}
}
public void Deposit(decimal amount)
{
lock (_lock)
{
_balance += amount;
}
}
}
// Usage:
// var account = new BankAccount(1000m);
// Parallel.For(0, 10, _ => account.Withdraw(100m));
// Console.WriteLine(account.Balance); // 0using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
// [step] Async-compatible mutex using SemaphoreSlim
public sealed class AsyncMutex : IDisposable
{
private readonly SemaphoreSlim _semaphore = new(1, 1);
private readonly ILogger<AsyncMutex> _logger;
private readonly string _name;
public AsyncMutex(string name, ILogger<AsyncMutex> logger)
{
_name = name;
_logger = logger;
}
// [step] Acquire with timeout and cancellation
public async Task<IDisposable> AcquireAsync(
TimeSpan? timeout = null,
CancellationToken ct = default)
{
var acquired = await _semaphore.WaitAsync(
timeout ?? Timeout.InfiniteTimeSpan, ct);
if (!acquired)
throw new TimeoutException(
$"Failed to acquire mutex '{_name}' within timeout");
_logger.LogDebug("Mutex '{Name}' acquired", _name);
return new MutexRelease(this);
}
private void Release()
{
_semaphore.Release();
_logger.LogDebug("Mutex '{Name}' released", _name);
}
public void Dispose() => _semaphore.Dispose();
private sealed class MutexRelease(AsyncMutex mutex) : IDisposable
{
private int _disposed;
public void Dispose()
{
if (Interlocked.Exchange(ref _disposed, 1) == 0)
mutex.Release();
}
}
}
// [step] Distributed-style mutex with named locks
public sealed class NamedLockManager(
ILogger<NamedLockManager> logger) : IDisposable
{
private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new();
public async Task<IDisposable> AcquireAsync(
string key, TimeSpan? timeout = null,
CancellationToken ct = default)
{
var sem = _locks.GetOrAdd(key, _ => new SemaphoreSlim(1, 1));
var acquired = await sem.WaitAsync(
timeout ?? TimeSpan.FromSeconds(30), ct);
if (!acquired)
throw new TimeoutException(
$"Timeout acquiring lock for '{key}'");
logger.LogDebug("Lock acquired: {Key}", key);
return new LockRelease(sem, key, logger);
}
public void Dispose()
{
foreach (var sem in _locks.Values)
sem.Dispose();
_locks.Clear();
}
private sealed class LockRelease(
SemaphoreSlim sem, string key, ILogger logger) : IDisposable
{
private int _disposed;
public void Dispose()
{
if (Interlocked.Exchange(ref _disposed, 1) == 0)
{
sem.Release();
logger.LogDebug("Lock released: {Key}", key);
}
}
}
}
// [step] Thread-safe resource with async mutex
public sealed class CriticalSection<T>(
T resource, ILogger<AsyncMutex> mutexLogger) where T : class
{
private readonly AsyncMutex _mutex = new("critical-section", mutexLogger);
public async Task<TResult> ExecuteAsync<TResult>(
Func<T, Task<TResult>> action,
TimeSpan? timeout = null,
CancellationToken ct = default)
{
using var _ = await _mutex.AcquireAsync(timeout, ct);
return await action(resource);
}
public async Task ExecuteAsync(
Func<T, Task> action,
TimeSpan? timeout = null,
CancellationToken ct = default)
{
using var _ = await _mutex.AcquireAsync(timeout, ct);
await action(resource);
}
}Mutex / Lock Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Mutex / Lock Pattern in the Real World
“A single-occupancy public restroom with a door latch is a perfect mutex. The latch (lock) ensures only one person (thread) occupies the restroom (critical section) at a time. Anyone who finds the door locked must wait outside; when the occupant leaves and unlatches the door, one waiting person may enter.”