.NET 中的轻量级线程安全

对线程安全有要求的代码中,通常会使用锁(lock)。自 .NET 诞生以来就有锁,然而从 .NET Framework 4.0 开始,又诞生了 6 个轻量级的线程安全方案:SpinLock, SpinWait, CountdownEvent, SemaphoreSlim, ManualResetEventSlim, Barrier


SpinLock, SpinWait

SpinLock 被称之为“自旋锁”,SpinWait 称为“自旋等待”,适合在非常轻量的计算中使用。它与普通 lock 的区别在于普通 lock 使用 Win32 内核态对象来实现等待,Overview of Synchronization Primitives 中描述为:

you can use synchronization primitives that provide fast performance by avoiding expensive reliance on Win32 kernel objects such as wait handles whenever possible.

在这个过程中,调用线程会挂起,并造成线程的上下文切换,而这是一部分不算小的开销。

自旋等待则是继续让 CPU 执行此线程,直到锁释放。在这个过程中,此线程会持续占用 CPU 资源,但避免了线程上下文切换。所以,对于短时间的计算采用 SpinLock 实现线程安全会更加高效;而长时间的任务执行会导致占用 CPU 资源从而导致其他任务执行所需的资源减少。

CountdownEvent

并行执行一些任务之后,通常还会继续执行一些代码。初始化时设置信号量次数,随后在每一个子任务结束之后设置一个信号量(调用其 Signal 方法)可以使计数减 1.这样,在调用 Wait 等待的地方就会等计数为 0 后继续执行。

SemaphoreSlim, ManualResetEventSlim

SemaphoreSlimManualResetEventSlim 是此前 SemaphoreManualResetEvent 的轻量级版本,从其名字“slim”便能看出来。

如何轻量

这些轻量级线程同步方案因为没有使用到 Win32 内核对象,而是在 .NET 内部完成,所以只能进行线程之间的同步,不能进行跨进程同步。如果要完成跨进程的同步,需要使用 MonitorMutex 这样的方案。


参考资料

本文会经常更新,请阅读原文: https://walterlv.com/post/lightweight-thread-safe-since-dotnet-4.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 吕毅 (包含链接: https://walterlv.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 (walter.lv@qq.com)