多线程2:线程同步
多个线程共享统一资源时,如果一个线程正在消费该资源,其他线程需要依次等待,这种情况称为线程同步。
1、原子操作
只占用一个量子的时间,一次就可以完成的操作,称为原子操作。
在一个原子操作过程中,只有当前操作完成,其他线程才能执行其他操作。
原子操作无需其他线程等待,这就避免了使用锁,也排除了死锁的情况。
Interlocked类提供了Increment、Decrement和Add等基本数学操作的原子方法。
2、线程同步的3种方式
(1) 内核模式
只有操作系统内核才能阻止线程使用CPU时间,因此该模式称为内核模式。
内核模式将等待的线程置于阻塞状态,从而占用尽可能少的CPU时间。
内核模式需要引入至少一次上下文切换(context switch),即操作系统的线程调度,这需要消耗大量资源。
线程调度器会保存等待线程的状态,并切换到另一个线程,依次恢复等待的线程的状态。
当线程需要挂起很长时间时,内核模式同步线程才有意义。
(2) 用户模式
当线程只需要等待一小段时间,最好是简单的等待,那么不将线程切换到阻塞状态的线程同步称为用户模式。
用户模式下线程等待时会浪费CPU时间,但是节省了切换线程上下文的CPU时间等资源。
该模式轻量、速度很快,适合于等待时间较短的线程等待。
(3) 混合模式(hybird)
混合模式会先尝试使用用户模式等待,如果线程等待了足够长的时间,则会切换到阻塞状态以节省CPU时间。
3、互斥量Mutex
具名的Mutex是全局的操作系统对象,务必正确关闭互斥量,可以使用using代码块包裹互斥量对象。
具名的Mutex可用于不同进程的线程同步。
4、信号量Semaphore
信号量可以指定允许的并发数量。
当信号量达到指定的并发数量后,新的线程需要等待,直到之前的线程中的某一个释放信号量发出信号。
SemaphoreSlim使用了混合模式,允许在等待时间很短的情况下无需切换上下文。
Semaphore使用纯粹的内核时间(kernel-time)方式,只有在非常重要的场景才需要使用。
具名的Semaphore和具名的Mutex一样,可以在不同进程之间进行线程同步。
SemaphoreSlim不使用Windows内核信号量,也不支持进程间线程同步。
5、自动事件锁AutoResetEvent
AutoResetEvent构造函数参数为false时,初始化没有信号,调用AutoResetEvent.WaitOne方法时会被阻塞,直到调用AutoResetEvent.Set方法。
AutoResetEvent构造函数参数为true时,初始化有信号,调用AutoResetEvent.WaitOne方法会被立即处理,然后事件状态变为无信号。
AutoResetEvent类采用的是内核时间模式。
6、手动事件锁ManualResetEventSlim
ManualResetEventSlim是ManualResetEvent的混合版本,一直有信号直到手动调用Reset方法。
调用Set方法时,信号触发允许准备好的线程接收信号并继续工作。
ManualResetEventSlim类采用的是混合模式。
如果需要全局事件,可以使用EventWaitHandle类,它是AutoResetEvent和ManualResetEvent的基类。
7、CountDownEvent
CountDownEvent表示在计数变为0时处于有信号状态的同步基元。
CountDownEvent在构造函数中指定收到信号的次数,并在达到该次数时解除对其等待线程的锁定。
针对需要等待多个异步操作完成的情形,可以使用该方式。
需要注意的是,使用CountDownEvent时要确保所有线程完成后都要调用Signal方法。
8、Barrier
Barrier可以使多个任务采用并行方式依据某种算法在多个阶段中协同工作。
Barrier在构造函数中指定需要同步的线程个数,所需线程会在调用Barrier.SignalAndWait时阻塞,
直到所有线程都调用该方法后会执行一个回调函数来完成该阶段,参与线程继续往下执行代码。
9、ReaderWriterLockSlim
ReaderWriterLockSlim读锁(ReadLock)允许多线程读取数据,写锁(WriteLock)会在释放前阻塞其他线程的所有操作。
ReaderWriterLockSlim的可升级读锁(UpgradeableReadLock)在读取数据时,可以获取一个写锁来修改数据。
应始终使用try/finally代码块来确保在获取锁后一定会释放锁。
10、SpinWait
volatile关键字指出一个字段可能会被同时执行的多个线程修改。
声明为volatile的字段不会被编译器和处理器优化为只能被单个线程访问,以确保字段值总是最新的。
SpinWait在开始时尝试使用用户模式,在9个迭代后开始切换为阻塞状态(内核模式),从而不会有CPU负载。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器