阻塞

  • 如果线程的执行由于某种原因导致暂停,那么就认为该线程被阻塞了。
    • 例如在Sleep或者通过Join等待其他线程结束。
  • 被阻塞的线程会立即将其处理器的时间片生成给其它线程,从此就不再消耗处理器时间,直到满足其阻塞条件为止。
  • 可以通过ThreadState这个属性来判断线程是否处于被阻塞的状态:
    • bool`` blocked = (someThread.ThreadState&ThreadState.WaitSleepJoin)!=0

ThreadState

  • ThreadState是一个flags enum,通过按位的形式,可以合并数据的选项。

image.png
image.png

  • 但是它大部分枚举值都没什么用,下面的代码将ThreadState剥离为四个最有用的值之一:Unstarted、Runing、WaitSleepJoin和Stopped

    1. //如果线程是下面几个状态就返回这个状态,否则就是Running状态
    2. public static ThreadState SimpleThreadState(ThreadState ts)
    3. {
    4. return ts & (ThreadState.Unstarted | ThreadState.WaitSleepJoin | ThreadState.Stopped);
    5. }
  • ThreadState属性可用于诊断的目的,但不适用于同步,因为线程状态可能会在测试ThreadState和对该信息进行操作之间发生变化。

解除阻塞Unblocking

  • 当遇到下列四种情况的时候,就会解除阻塞:
    • 阻塞条件被满足
    • 操作超时(如果设置超时的话)
    • 通过Thread.Interrupt()进行打断
    • 通过Thread.Abort()进行中止

上下文切换

通过上面情况,可能会发生切换。

  • 当线程阻塞或解除阻塞的时,操作系统将进行上下文切换。这会产生少量开销,通常为1或2微妙。

I/O-bound VS Compute-bound 或(CPU-Bound)

CPU-bound(计算密集型) 和I/O bound(I/O密集型)

  • 一个花费大部分时间等待某事发生的操作成为I/O-bound
    • I/O绑定操作通常涉及输入或输出,但这不是硬性要求:Thread.Sleep()也被视为I/O-bound
  • 相反,一个花费大部分时间执行CPU密集型工作的操作成为Compute-bound。

阻塞vs忙等待(自选) Blocking vs Spinning

  • IO-bound操作的工作方式有两种:
    • 在当前线程上同步的等待
      • Console.ReadLine(),Thread.Sleep(),Thread.Join()…
    • 异步的操作,在稍后操作完成时触发一个回调动作。
  • 同步等待的I/O-bound操作将大部分时间花在阻塞线程上。
  • 它们也可以周期性的在一个循环里进行“打转(自旋)” ```csharp // 示例 while(DateTime.Now < nextStartTime) Thread.Slepp(100);

while(DateTime.Now < nextStartTime); ```

  • 在忙等待和阻塞方面有一些细微差别。
    • 首先,如果您希望条件很快得到满足(可能在几微妙之内),则短暂自选可能会很有效,因为它避免了上下文切换的开销和延迟。
      • .NET Framework提供了特殊的方法和类来提供帮助SpinLock和SpinWait。
    • 其次,阻塞也不是零成本。这是因为每个线程在生存期间会占用大约1MB的内存,并会给CLR和操作系统带来持续的管理开销。
      • 因此,在需要处理成百上千个并发操作的大量I/O-bound程序的上下文中,阻塞可能会很麻烦
      • 所以,此类程序需要使用给基于回调的方法,在等待时完全撤销其线程。