目录
1.阻塞(Blocking)
2.阻塞 VS 轮询
3.线程状态
到目前为止,我们已经阐述了如何在线程上启动任务、配置线程以及实现双向数据传递。同时,我们也说明了局部变量是线程私有的,而引用可以通过共享字段在线程间传递以实现通信。
下一步的关键是同步机制:通过协调线程行为来获得可预测的结果。当多个线程访问同一数据时,同步显得尤为重要——这个领域看似简单却暗藏风险。
同步构造可分为四大类别:
-
简单阻塞方法
这类方法通过等待其他线程结束或计时完成来实现同步。例如:Sleep
、Join
和Task.Wait
都属于简单阻塞方法。 -
锁定构造
用于限制同时执行某段代码或操作的线程数量。最常见的独占锁(如lock
/Monitor.Enter
/Monitor.Exit
、Mutex
和SpinLock
)仅允许一个线程进入,确保竞争线程访问共享数据时互不干扰。非独占锁包括信号量(Semaphore
/SemaphoreSlim
)和读写锁。 -
信号构造
允许线程暂停运行直至接收到其他线程的通知,从而避免低效的轮询。常用信号机制有两种:事件等待句柄(event wait handles)和Monitor
的Wait
/Pulse
方法。.NET Framework 4.0 新增了CountdownEvent
和Barrier
类。 -
非阻塞同步构造
通过调用处理器原语来保护共享字段的访问。CLR 和 C# 提供的非阻塞构造包括:Thread.MemoryBarrier
、Thread.VolatileRead
、Thread.VolatileWrite
、volatile
关键字以及Interlocked
类。
除最后一类外,阻塞机制在其他类别中均占核心地位。下面我们将简要探讨这一概念。
1.阻塞(Blocking)
当线程因某些原因暂停执行时(例如通过 Sleep
进入休眠,或通过 Join
/EndInvoke
等待其他线程结束),该线程即被视为阻塞状态。阻塞线程会立即释放其处理器时间片,此后在阻塞条件满足前不再消耗任何处理器资源。可通过线程的 ThreadState
属性检测阻塞状态:
bool blocked = (someThread.ThreadState & ThreadState.WaitSleepJoin) != 0;
(由于线程状态可能在检测与后续操作之间发生变化,此代码仅适用于诊断场景。)
当线程阻塞或解除阻塞时,操作系统会执行上下文切换。这将产生几微秒的开销。
线程会通过以下四种方式解除阻塞(当然,按电脑电源键不算!):
-
阻塞条件得到满足
-
操作超时(如果指定了超时时间)
-
通过Thread.Interrupt被中断
-
通过Thread.Abort被中止
需要注意的是,如果线程是通过(已弃用的)Suspend方法暂停执行的,则不会被判定为处于阻塞状态。
2.阻塞 VS 轮询
当线程需要暂停直到特定条件满足时,通常有两种实现方式:
-
阻塞等待(高效方式)
通过信号和锁机制实现,线程会进入阻塞状态直到条件满足,此时操作系统会调度其他线程执行。 -
轮询等待(简单但低效)
线程通过循环检测条件来实现等待,例如:
while (!proceed); // 忙等待
或
while (DateTime.Now < nextStartTime); // 时间等待
性能考量:
-
纯轮询会完全占用CPU资源,因为CLR和操作系统会认为线程正在进行重要计算
-
这种方式的CPU利用率是100%,极其浪费系统资源
改进方案:
可以采用混合阻塞的方式:
while (!proceed) Thread.Sleep(10); // 每次检查后休眠10ms
虽然不够优雅,但相比纯轮询能显著降低CPU占用率。不过需要注意共享变量(如proceed标志)的并发访问问题,正确的锁和信号量使用可以避免这些问题。
适用场景:
当预期条件能在极短时间内(如几微秒)满足时,短暂轮询可能更高效,因为它避免了上下文切换的开销和延迟。.NET框架为此提供了专门的工具类和方法,这些内容将在并行编程章节详细介绍。
这里小节一下:
-
阻塞等待:适合大多数情况,资源利用率高
-
纯轮询:简单但资源浪费严重
-
混合模式:折中方案,需要处理并发问题
-
微秒级等待:特殊场景下短暂轮询可能更优
3.线程状态
您可以通过ThreadState属性查询线程的执行状态。该属性返回一个ThreadState类型的标志枚举,它以位运算方式组合了三个"层次"的数据。不过,大多数枚举值都是冗余的、未使用的或已废弃的。下图展示了其中一个"层次":
以下代码可将ThreadState精简为四个最常用的状态值:未启动(Unstarted)、运行中(Running)、等待休眠或加入(WaitSleepJoin)和已停止(Stopped):
public static ThreadState SimpleThreadState(ThreadState ts)
{return ts & (ThreadState.Unstarted |ThreadState.Running |ThreadState.WaitSleepJoin |ThreadState.Stopped);
}
ThreadState属性适用于诊断目的,但不适合用于同步控制,因为在检测线程状态和基于该状态执行操作之间,线程状态可能会发生变化。
本节完,下一节将开始介绍同步工具