自旋和阻塞

  线程等待有内核模式(Kernel Mode)和用户模式(User Model)。

  用户模式:同步机制(轮训CPU,不用上下文切换,合适等待时间短的操作),不切换上下文,消耗CPU

  内核模式:构造(需要上下文切换、消耗操作系统资源),切换上下文,消耗系统资源

  因为只有操作系统才能控制线程的生命周期,因此使用 Thread.Sleep() 等方式阻塞线程,发生上下文切换,此种等待称为内核模式。

  用户模式使线程等待,并不需要线程切换上下文,而是让线程通过执行一些无意义的运算,实现等待。也称为自旋。

  线程阻塞是会耗费上下文切换的,对于过短的线程等待,这种切换的代价会比较昂贵的。在我们前面的示例中,大量使用了 Thread.Sleep() 和各种类型的等待方法,这其实是不合理的。

  SpinWait 则提供了更好的选择。

/**
属性:
Count 获取已对此实例调用 SpinOnce() 的次数。
NextSpinWillYield 获取对 SpinOnce() 的下一次调用是否将产生处理器,同时触发强制上下文切换。
方法:
Reset() 重置自旋计数器。
SpinOnce() 执行单一自旋。
SpinOnce(Int32) 执行单一自旋,并在达到最小旋转计数后调用 Sleep(Int32) 。
SpinUntil(Func) 在指定条件得到满足之前自旋。
SpinUntil(Func, Int32) 在指定条件得到满足或指定超时过期之前自旋。
SpinUntil(Func, TimeSpan) 在指定条件得到满足或指定超时过期之前自旋。 */


// 使用volatile对字段操作,总是直接对RAM进行操作,而不会对CPU寄存器进行操作
static volatile bool isCompleted = false;// volatile允许多个线程访问,呈现最新的

/// 
/// 用户模型等待
/// 
static void UserModelWait()
{
  while (!isCompleted)
  {
    Console.WriteLine(".");
  }
  Console.WriteLine("UserModelWait:Waiting is complete");
}

/// 
/// 自旋等待
/// 一个轻量同步类型(结构体),提供对基于自旋的等待的支持。
/// SpinWait只有在多核处理器下才具有使用意义。
/// 在单处理器下,自旋转会占据CPU时间,却做不了任何事。
/// SpinWait并没有设计为让多个任务或线程并发使用。
/// 因此,如果多个任务或者线程通过SpinWait的方法进行自旋,
/// 那么每一个任务或线程都应该使用自己的SpinWait实例。
/// 
static void HybridSpinWait()
{
  SpinWait w = new();
  while (!isCompleted)
  {
    w.SpinOnce();// 执行单一自旋。
    // 判断对SpinWait.SpinOnce() 的下一次调用是否触发上下文切换和内核转换。
    // 由NextSpinWillYield属性代码可知,若SpinWait运行在单核计算机上,它总是进行上下文切换(让出处理器)。
    // SpinWait不仅仅是一个空循环。它经过了精心实现,可以针对一般情况提供正确的旋转行为以避免内核事件所需的高开销的上下文切换和内核转换;
    // 在旋转时间足够长的情况下自行启动上下文切换,SpinWait甚至还会在多核计算机上产生线程的时间片(Thread.Yield())以防止等待线程阻塞高优先级的线程或垃圾回收器线程。
    // NextSpinWillYield:判断是否切换到内核模式,获取对 SpinOnce() 的下一次调用是否将产生处理器,同时触发强制上下文切换。
    Console.WriteLine(w.NextSpinWillYield);
  }
  Console.WriteLine("HybridSpinWait:Waiting is complete");
}

static void Main(string[] args)
{
  Console.WriteLine("Running user mode waiting");
  new Thread(UserModelWait).Start();
  Thread.Sleep(20);
  isCompleted = true;

  Thread.Sleep(TimeSpan.FromSeconds(1));

  isCompleted = false;

  Console.WriteLine("Running hybrid SpinWait construct waiting");
  new Thread(HybridSpinWait).Start();
  Thread.Sleep(20);
  isCompleted = true;
}

  定义一个线程,将执行一个无止境的循环,直到20毫秒后主线程设置isCompleted变量为true,我们可以试验周期为20-30秒,通过windows任务管理器测量CPU的负载情况。

  用volatie关键字来声明iscompleted静态字段,volatie字段不会被编辑器和处理器优化,只能被单个线程访问。