Skip to content
Go back

.NET 进程内同步 API 全览:从 lock 到 Barrier

.NET 进程内同步 API

多线程编程里,同步是绕不过去的话题。用不好,轻则数据错乱,重则程序卡死。这篇文章来自 Ricardo Peres 的系列博客 Development with a Dot,系统整理了 .NET 进程内(in-process)所有主要的同步原语,并给出分类对照和实用建议。

这是该系列第一篇,后续还会覆盖同机器上的跨进程同步和分布式同步。


为什么需要同步

多线程共享资源时,如果不加控制,会出现:


各类 API 详解

lock 语句

lock 是 C# 语言内置的关键字,是 MonitorLock 类的语法糖,确保同一时刻只有一个线程执行代码块。即使块内抛出异常,锁也会被正确释放。

object _lock = new object();

lock (_lock)
{
    // 临界区
}

注意:不要对 this、静态字段或类型本身加锁,这是常见的误用。

Lock 类(.NET 8+)

从 .NET 8 起,如果 lock 的参数类型是 Lock,编译器会改用 Lock.EnterScope() 实现,返回一个 Lock.Scoperef structusing 结束时自动释放):

var lockObj = new Lock();

using (lockObj.EnterScope())
{
    // 临界区
}

Lock.Scope 不实现 IDisposable(因为是 ref struct),但 using 语句仍会隐式调用它的 Dispose 方法。

Monitor

Monitorlock 语句在 .NET 9 之前的底层实现,提供编程式访问:

object _lock = new object();

try
{
    Monitor.Enter(_lock);
    // 临界区
}
finally
{
    Monitor.Exit(_lock);
}

Monitor 额外提供三个协作方法:

还有 TryEnter(.NET 9 新增),与 Enter 的区别在于:前者无法获得锁时立即返回(可带超时),后者会一直阻塞。

除非需要 Pulse/Wait 这类高级协作场景,否则直接用 lock 就够了。

[MethodImpl] 特性

[MethodImpl(MethodImplOptions.Synchronized)] 加在方法上,效果等同于把整个方法体包在 lock 里:实例方法锁 this,静态方法锁类型本身。

[MethodImpl(MethodImplOptions.Synchronized)]
public void SynchronizedMethod()
{
    // 临界区
}

这违反了”不要锁 this”的准则,但作为快速方案有其便利之处。

Mutex

Mutex 提供互斥访问,确保同一时刻只有一个线程进入临界区。它工作在内核层,支持锁所有权(只有持锁线程才能释放),也是本系列后续跨进程同步的基础。

using var mutex = new Mutex(initiallyOwned: false);

mutex.WaitOne();          // 获取锁(可带超时)
// 临界区
mutex.ReleaseMutex();     // 释放锁

Mutex 只提供同步方法,内核调用开销相对较大。

Semaphore 与 SemaphoreSlim

Semaphore 允许设定最大并发访问数,是经典的计数信号量,工作在内核层:

using var semaphore = new Semaphore(initialCount: 1, maximumCount: 3);

semaphore.WaitOne();
// 临界区
semaphore.Release(releaseCount: 1);

SemaphoreSlim 是用户级(纯托管代码)的轻量版本,主要优势是支持异步等待

using var semaphore = new SemaphoreSlim(initialCount: 1, maximumCount: 3);

await semaphore.WaitAsync();
// 临界区
semaphore.Release(releaseCount: 1);

Mutex 不同,Semaphore 没有锁所有权概念,任何线程都可以调用 Release

ReaderWriterLock 与 ReaderWriterLockSlim

这两个类支持读写分离:多个读者可并发持有读锁,但写锁是独占的。适合读多写少的场景。

ReaderWriterLock(内核级):

using var rwLock = new ReaderWriterLock();

// 读者
rwLock.AcquireReaderLock(Timeout.Infinite);
// ...读操作
rwLock.ReleaseReaderLock();

// 写者
rwLock.AcquireWriterLock(Timeout.Infinite);
// ...写操作
rwLock.ReleaseWriterLock();

还支持从读锁升级为写锁(UpgradeToWriterLock)然后降级(DowngradeFromWriterLock)。

ReaderWriterLockSlim(用户级)是推荐版本,提供 TryEnter* 系列方法,支持可升级读锁(EnterUpgradeableReadLock),并且支持异步场景。

事件类

事件类(AutoResetEventManualResetEvent)是内核级对象,用于线程间信号通知:

using var evt = new AutoResetEvent(initialState: false);

// 线程 A:等待信号
evt.WaitOne();

// 线程 B:发出信号
evt.Set();

ManualResetEventSlim 是用户级轻量版,行为与 ManualResetEvent 相同,但不支持跨进程。

也可以通过基类 EventWaitHandleEventResetMode 标志来灵活选择模式。

CountdownEvent

CountdownEvent 在被信号通知指定次数后,才释放所有等待线程:

var countdown = new CountdownEvent(10);

// 等待方
countdown.Wait();

// 信号方(调用 10 次后等待方才会继续)
countdown.Signal();

没有锁所有权,任何线程都可以调用 Signal

SpinLock 与 SpinWait

这两个是用户级自旋原语,在锁竞争极低、持锁时间极短的场景下有性能优势,因为它们避免了线程上下文切换。

SpinLock 适合保护短小的临界区:

var spinLock = new SpinLock();
var lockTaken = false;

try
{
    spinLock.Enter(ref lockTaken);
    // 临界区
}
finally
{
    if (lockTaken) spinLock.Exit();
}

SpinWait 适合等待某个条件成立:

SpinWait.SpinUntil(() => someCondition, TimeSpan.FromSeconds(1));

Barrier

Barrier 用于多个线程(参与者)在某个阶段完成后集体同步,再一起推进下一阶段:

using var barrier = new Barrier(participantCount: 3, postPhaseAction: b =>
{
    // 每个阶段完成后执行的整合操作
});

Action participant = () =>
{
    // 做本阶段工作
    barrier.SignalAndWait(); // 等所有参与者到达后一起继续
};

Parallel.Invoke(participant, participant, participant);

每次所有参与者都调用 SignalAndWait() 后,屏障会递增 CurrentPhaseNumber 并重置,可无限复用。也可以动态增减参与者。


基类:WaitHandle 与 EventWaitHandle

WaitHandleAutoResetEventManualResetEventMutexSemaphore 的公共抽象基类,提供:

Mutex[] mutexes = { ... };

WaitHandle.WaitAll(mutexes);
int first = WaitHandle.WaitAny(mutexes);

EventWaitHandleAutoResetEventManualResetEvent 的直接基类,定义了 SetReset 等方法。


死锁

死锁的常见原因:


分类汇总

维度单种模式读写分离
访问模式lock/Lock/Monitor/Mutex/Semaphore/SemaphoreSlim/SpinLock/SpinWait/Event 类/BarrierReaderWriterLock/ReaderWriterLockSlim
维度同步异步
是否支持异步lock/Monitor/Mutex/Semaphore/ReaderWriterLock/SpinLock/Event 类/CountdownEvent/BarrierSemaphoreSlim/ReaderWriterLockSlim
维度内核级用户级(托管)
运行层级Mutex/Semaphore/ReaderWriterLock/Event 类/CountdownEventLock/Monitor/SemaphoreSlim/ReaderWriterLockSlim/SpinLock/SpinWait/Barrier

使用建议


参考


Tags


Previous

用 MeterListener 在进程内采集 .NET 指标

Next

EF Core 10 批量操作全攻略:插入、更新、删除的策略与性能对比