Semaphore

Intro

Semaphore controls concurrent access by allowing up to N holders at once, unlike Mutex and lock which allow exactly one. This is the right primitive when you need bounded parallelism — for example, limiting an HTTP client to 10 concurrent outbound requests to avoid overwhelming a downstream API (which returns 429 at 15 concurrent connections), or capping database connection usage below the pool maximum during batch processing. In modern .NET, SemaphoreSlim is preferred for in-process async workflows because it supports WaitAsync and avoids kernel transitions.

How It Works

A semaphore tracks a permit count:

Example

using var gate = new SemaphoreSlim(initialCount: 4, maxCount: 4);

await gate.WaitAsync(cancellationToken);
try
{
    await ProcessAsync(cancellationToken);
}
finally
{
    gate.Release();
}

Named Semaphore for cross-process bounded access:

// Limit 3 concurrent processes accessing a shared resource
const string SemName = "MyApp.ResourceGate";
using var sem = new Semaphore(initialCount: 3, maximumCount: 3, name: SemName);

if (!sem.WaitOne(TimeSpan.FromSeconds(5)))
    throw new TimeoutException("Could not acquire semaphore slot.");
try
{
    AccessSharedResource();
}
finally
{
    sem.Release();
}

Pitfalls

Tradeoffs

Questions


Whats next