详解c# SpinWait

SpinWait封装常见旋转逻辑。在单处理器计算机上,始终使用 "生成" 而不是 "繁忙等待",在装有超线程技术的 Intel 处理器的计算机上,这有助于防止硬件线程不足。SpinWait 封装了一种很好的旋转和真正的生成。

SpinWait是一个值类型,这意味着低级别代码可以使用 SpinWait,而不必担心不必要的分配开销。SpinWait 对于普通应用程序通常不起作用。在大多数情况下,应使用由 .NET Framework 提供的同步类,如 Monitor 。但在需要自旋等待的大多数情况下, SpinWait 类型应优先于 Thread.SpinWait 方法。

System.Threading.SpinWait 是一种轻型同步类型,可用于低级方案,以避免执行内核事件所需的高成本上下文切换和内核转换。在多核计算机上,如果不得长时间保留资源,更高效的做法是,先让等待线程在用户模式下旋转几十或几百个周期,再重试获取资源。如果资源在旋转后可用,便节省了几千个周期。如果资源仍不可用,那么也只花了几个周期,仍可以进入基于内核的等待。这种“旋转后等待”的组合有时称为“两阶段等待操作” 。

SpinWait 旨在与包装内核事件(如 ManualResetEvent)的 .NET Framework 类型结合使用。SpinWait 本身也可以仅在一个程序中用于提供基本的旋转功能。

SpinWait 不仅仅只是空循环。谨慎实现后,它可以提供适用于一般情况的正确旋转行为,并且本身能够在旋转时间够长(大致是内核转换所需的时间长度)时自行启动上下文切换。例如,在单核计算机上,SpinWait 会立即生成线程的时间片,因为旋转会阻止所有线程取得进展。即使在多核计算机上,SpinWait 也会生成时间片,以防等待线程阻止优先级较高的线程或垃圾回收器。因此,若要在两阶段等待操作中使用 SpinWait,建议在 SpinWait 本身启动上下文切换前,先调用内核等待。SpinWait 提供每次调用 SpinOnce 前都可以检查的 NextSpinWillYield 属性。如果此属性返回 true,启动自己的等待操作。



看完官方说明一脸懵逼,将上面的语言用通俗的话来说,Thread.Sleep方法在执行时,会将阻止的时间的CPU切换至其他等待的进程,等到Thread.Sleep等待时间到后,再获取CPU的控制权继续执行下一步操作;SpinWait提供了While循环方法,在等待通过循环来阻止当前CPU的释放,一直等待当前方法执行完成然后释放。我们都知道进程在切换的时候会有时间与内存的消耗,所以尽可能使用SpinWait替代Thread.Sleep。

现在我们看下SpinWait结构中的代码:

/// <summary>
/// 循环一次
/// </summary>
/// <remarks>
/// This is typically called in a loop, and may change in behavior based on the number of times a
/// <see cref="SpinOnce"/> has been called thus far on this instance.
/// </remarks>
public void SpinOnce()
{
  if (NextSpinWillYield)
  {
    int yieldsSoFar = (m_count >= YIELD_THRESHOLD ? m_count - YIELD_THRESHOLD : m_count);
    //③循环到20次时,执行Thread.Sleep(01)
    if ((yieldsSoFar % SLEEP_1_EVERY_HOW_MANY_TIMES) == (SLEEP_1_EVERY_HOW_MANY_TIMES - 1))
    {
      //当前线程挂起,让出cpu
      //所有挂起的线程都有机会竞争当前时间片段,不限制线程优先级
      Thread.Sleep(1);
    }
    //②执行Thread.Yield()5次后,执行Thread.Sleep(0)
    else if ((yieldsSoFar % SLEEP_0_EVERY_HOW_MANY_TIMES) == (SLEEP_0_EVERY_HOW_MANY_TIMES - 1))
    {
      //当前线程挂起,让出cpu
      //(只允许那些优先级相等或更高的线程使用当前的CPU。
      //如果没有,那当前线程会重新使用CPU时间片)
      //(上面已说明,后续补充实现)
      Thread.Sleep(0);
    }
    else
    {
      //当前线程挂起(执行状态->就绪状态), 让出cpu,
      //(后续补充实现逻辑)
      Thread.Yield();
    }
  }
  else
  {
    //线程等待
    //4,8,16,32,64...位运算,2的n次方
    //①循环10次
    Thread.SpinWait(4 << m_count);
  }
  // m_count 递增; m_count 达到最大值后回滚Count =10
  m_count = (m_count == int.MaxValue ? YIELD_THRESHOLD : m_count + 1);
}
/// <summary>
/// 重置循环计数器
/// </summary>
public void Reset()
{
  m_count = 0;
}
#region Static Methods
/// <summary>
/// 循环.直到condition返回True
/// </summary>
public static void SpinUntil(Func<bool> condition)
{
  SpinUntil(condition, Timeout.Infinite);
}
/// <summary>
/// 循环,直到condition返回True或者时间达到timeout
/// </summary>
public static bool SpinUntil(Func<bool> condition, TimeSpan timeout)
{
  //校验时间格式是否正确
  Int64 totalMilliseconds = (Int64)timeout.TotalMilliseconds;
  if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue)
  {
    throw new System.ArgumentOutOfRangeException(
      "timeout", timeout, "SpinWait_SpinUntil_TimeoutWrong");
  }
  return SpinUntil(condition, (int)timeout.TotalMilliseconds);
}
/// <summary>
/// 直到condition返回True或者时间达到timeout.
/// </summary>
public static bool SpinUntil(Func<bool> condition, int millisecondsTimeout)
{
  //校验时间格式
  if (millisecondsTimeout < Timeout.Infinite)
  {
    throw new ArgumentOutOfRangeException(
      "millisecondsTimeout", millisecondsTimeout, "SpinWait_SpinUntil_TimeoutWrong");
  }
  //空值校验
  if (condition == null)
  {
    throw new ArgumentNullException("condition", "SpinWait_SpinUntil_ArgumentNull");
  }
  uint startTime = 0;
  if (millisecondsTimeout != 0 && millisecondsTimeout != Timeout.Infinite)
  {
    //自上次启动计算机以来所经过的时间(以毫秒为单位)。
    startTime = TimeoutHelper.GetTime();
  }
  SpinWait spinner = new SpinWait();
  while (!condition())
  {
    if (millisecondsTimeout == 0)
    {
      return false;
    }
    spinner.SpinOnce();
    //计时
    if (millisecondsTimeout != Timeout.Infinite && spinner.NextSpinWillYield)
    {
      if (millisecondsTimeout <= (TimeoutHelper.GetTime() - startTime))
      {
        return false;
      }
    }
  }
  return true;
}

#endregion

以上就是详解c# SpinWait的详细内容,更多关于c# SpinWait的资料请关注我们其它相关文章!

(0)

相关推荐

  • c# 怎样简洁高效的实现多个 Enum 自由转换

    一:背景 1. 讲故事 前段时间和同事负责一个项目的两个业务模块,可能大家缺少沟通,导致本该定义一个 Enum 的地方结果我俩各自定义了一个,导致后面这两个 Enum 进行对接就烦了,为了方便理解,也不想让大家看这崴脚的英文拼写,我就拿 银行 举例吧. A同事 定义的枚举 public enum BankEnum { ICBC = 1, CMSB = 2, CMBC = 3 } B同事 定义的枚举 public enum ChinaBankEnum { 中国民生银行 = 1, 中国工商银行 =

  • C#中HttpWebRequest的用法详解

    本文实例讲述了C#中HttpWebRequest的用法.分享给大家供大家参考.具体如下: HttpWebRequest类主要利用HTTP 协议和服务器交互,通常是通过 GET 和 POST 两种方式来对数据进行获取和提交.下面对这两种方式进行一下说明: GET 方式: GET 方式通过在网络地址附加参数来完成数据的提交,比如在地址 http://www.jb51.net/?hl=zh-CN 中,前面部分 http://www.jb51.net表示数据提交的网址,后面部分 hl=zh-CN 表示附

  • C# DataGridView添加新行的2个方法

    可以静态绑定数据源,这样就自动为DataGridView控件添加 相应的行.假如需要动态为DataGridView控件添加新行,方法有很多种,下面简单介绍如何为DataGridView控件动态添加新行的两种方 法: 方法一: 复制代码 代码如下: int index=this.dataGridView1.Rows.Add();this.dataGridView1.Rows[index].Cells[0].Value = "1"; this.dataGridView1.Rows[inde

  • c#如何显式实现接口成员

    本示例声明一个接口IDimensions 和一个类 Box,显式实现了接口成员 GetLength 和 GetWidth. 通过接口实例 dimensions 访问这些成员. interface IDimensions { float GetLength(); float GetWidth(); } class Box : IDimensions { float lengthInches; float widthInches; Box(float length, float width) { l

  • c#的dllimport使用方法详解

    DllImport是System.Runtime.InteropServices命名空间下的一个属性类,其功能是提供从非托管DLL导出的函数的必要调用信息 DllImport属性应用于方法,要求最少要提供包含入口点的dll的名称.DllImport的定义如下: 复制代码 代码如下: [AttributeUsage(AttributeTargets.Method)]public class DllImportAttribute: System.Attribute{public DllImportA

  • 详解c# 强制转换和类型转换

    由于 C# 是在编译时静态类型化的,因此变量在声明后就无法再次声明,或无法分配另一种类型的值,除非该类型可以隐式转换为变量的类型. 例如,string 无法隐式转换为 int. 因此,在将 i 声明为 int 后,无法将字符串"Hello"分配给它,如以下代码所示: int i; // error CS0029: Cannot implicitly convert type 'string' to 'int' i = "Hello"; 但有时可能需要将值复制到其他类

  • c# 使用模式匹配以及 is 和 as 运算符安全地进行强制转换

    由于是多态对象,基类类型的变量可以保存派生类型. 要访问派生类型的实例成员,必须将值强制转换回派生类型. 但是,强制转换会引发 InvalidCastException 风险. C# 提供模式匹配语句,该语句只有在成功时才会有条件地执行强制转换. C# 还提供 is 和 as 运算符来测试值是否属于特定类型. 下面的示例演示如何使用模式匹配 is 语句: class Animal { public void Eat() { Console.WriteLine("Eating."); }

  • c# 实现雪花分形的示例

    C#都没人用了吗,网上想找个现成的雪花分形代码,都没找见,有C++,有python,有java的,就没有C#的,自己试试写一个吧. public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Paint(object sender, PaintEventArgs e) { DrawKochSnow(e.Graphics); } private void ZheXi

  • C# 一个WCF简单实例

    WCF实例(带步骤) 复制代码 代码如下: <xmlnamespace prefix ="o" ns ="urn:schemas-microsoft-com:office:office" /> 本篇转自百度文档,自己试过,确实可以用. 以订票为例简单应用wcf 新建一个wcf服务应用程序 在IService1.cs定义服务契约 复制代码 代码如下: namespace WcfDemo { // 注意: 如果更改此处的接口名称 "IService

  • C#连接MySql数据库的方法

    1.要连接MySql数据库必须首先下载MySql官方的连接.net的文件,文件下载地址为http://dev.mysql.com/downloads/connector/net/6.6.html#downloads ,下载平台选择.Net&Mono,下载ZIP免安装版.2.解压缩刚才下载的mysql-connector-net-6.6.6-noinstall.zip文件,里面有几个版本选择,在这里我选V4, 选中这几个文件,然后添加到C#项目的引用中,然后就可以编写程序进行数据库的操作了. 3.

随机推荐