锁提供了线程安全三要素中的 有序性。二、SpinWait是同步锁的核心,而Thread是SpinWait的核心。

Spinwait结构

完成代码:SpinWait.cs (dot.net)

SpinWait结构的核心代码

internal const int YieldThreshold = 10; // When to switch over to a true yield.
private const int Sleep0EveryHowManyYields = 5; // After how many yields should we Sleep(0)?
internal const int DefaultSleep1Threshold = 20; // After how many yields should we Sleep(1) frequently? private void SpinOnceCore(int sleep1Threshold)
{
if ((
_count >= YieldThreshold &&
((_count >= sleep1Threshold && sleep1Threshold >= 0) || (_count - YieldThreshold) % 2 == 0)
) ||
Environment.IsSingleProcessor)
{ if (_count >= sleep1Threshold && sleep1Threshold >= 0)
{
Thread.Sleep(1);
}
else
{
int yieldsSoFar = _count >= YieldThreshold ? (_count - YieldThreshold) / 2 : _count;
if ((yieldsSoFar % Sleep0EveryHowManyYields) == (Sleep0EveryHowManyYields - 1))
{
Thread.Sleep(0);
}
else
{
Thread.Yield();
}
}
}
else
{
//线程获取一个每次自旋迭代的最佳最大自旋等待
int n = Thread.OptimalMaxSpinWaitsPerSpinIteration;
if (_count <= 30 && (1 << _count) < n)
{
n = 1 << _count;//1向左移动_count位 .
}
Thread.SpinWait(n);
} // Finally, increment our spin counter.
_count = (_count == int.MaxValue ? YieldThreshold : _count + 1);
}

原理:SpinWait结构就是对Thread.SpinWait方法的一个简单包装,一个循环次数的策略的。

SpinOnce() 执行次数超过10次之后,每次进行自旋便会触发Thread.Yield()上下文切换的操作,在这之后每5次会进行一次sleep(0)操作,每20次会进行一次sleep(1)操作。SpinOnce()执行一次是大概7个时钟周期。第一自旋例外,第一次的时候比较耗时。

SpinOnce(int32 num) 表示执行num次后进入sleep(1);

Thread.Sleep(0) 表示让优先级高的线程插队,如果没有线程优先级更高线程插队,那么就继续执行。
Thread.Yield() 让有需要的线程先执行,忽略线程优先级(低级别的线程也可以在core运行),而该线程进入就绪队列。

SpinWait 内部用的是Thread.SpinWait(),Thread.SpinWait()调用外部函数(private static extern void SpinWaitInternal(int iterations);)现实自旋。

 Thread.SpinWait()的注解

Thread.SpinWait本质上是将处理器放入一个非常紧凑的循环中,循环计数由迭代参数指定。因此,等待的时间长短取决于处理器的速度。说白了SpinWait就是一个for循环,我们只要输入一个数字就行。总的循环的时间由处理器的处理速度决定。
SpinWait对于普通应用程序通常不是很有用。在大多数情况下,应该使用.net框架提供的同步类

在极少数情况下,避免使用上下文切换很有用,例如,当你知道状态更改即将发生时,请 SpinWait 在循环中调用方法。 执行的代码 SpinWait 旨在防止具有多个处理器的计算机上出现的问题。 例如,在具有多个采用 Hyper-Threading 技术的 Intel 处理器的计算机上, SpinWait 在某些情况下防止处理器不足。

属性

count 表示第几次执行SpinWait

isNextSpinWillYield :thread.SpinWait执行超过10次,开始yield模式
源代码:

    public bool NextSpinWillYield
{
get
{
if (_count < 10)
{
return Environment.IsSingleProcessor;
}
return true;
}
}

方法

Reset();将count设置未0。

SpinUntil(Func<Boolean>)     在指定条件得到满足之前自旋。内部使用的是 while (!condition()) {spinWait.SpinOnce() 其他代码}
SpinUntil(Func<Boolean>, Int32)     在指定条件得到满足或指定超时过期之前自旋。内部使用的是spinWait.SpinOnce()
SpinUntil(Func<Boolean>, TimeSpan)     在指定条件得到满足或指定超时过期之前自旋。内部使用的是spinWait.SpinOnce()

SpinOnce(int32 num) 中 num 表示执行num次SpinOnce()操作后进入sleep(1);

SpinOnce()\SpinOnce(int32 num):

默认执行20次SpinOnce()操作后进入 sleep(1)。执行SpinOnce()在次数超过10之后,每次进行便会触发Thread.Yield()上下文切换的操作,在这之后每5次会进行一次sleep(0)操作,每20次会进行一次sleep(1)操作。

SpinOnce()和SpinOnce(int32 num)方法前10次调用的是Thread.SpinWait()方法;

前10次 源代码

int n = Thread.OptimalMaxSpinWaitsPerSpinIteration;//大概10以内,可以通过vs2022 调试源代码查看该变量。
if (_count <= 30 && (1 << _count) < n)
{
n = 1 << _count;//1向左位移_count
}
Thread.SpinWait(n);//是一个循环、n是循环的次数

那么在 10 次调用之后呢?

10 次之后 SpinOnce 就不再进行 Spin 操作了,它根据情况选择进入不同的 Yield 流程。

使用场合:

1、只能在进程内的线程使用。

因为他是轻量级锁。轻量级线程同步方案因为没有使用到 Win32 内核对象,而是在 .NET 内部完成,所以只能进行线程之间的同步,不能进行跨进程同步。如果要完成跨进程的同步,需要使用 MonitorMutex 这样的方案。

2、适合在非常轻量的计算中使用。

它与普通 lock 的区别在于普通 lock 使用 Win32 内核态对象来实现等待

 使用要点:

1、如果等待某个条件满足需要的时间很短(几毫秒),而且不希望发生昂贵的上下文切换,那么基于自旋的等待是一种很好的替换方案,SpinWait不仅提供了基本自旋功能,而且还提供了SpinWait.SpinUntil方法,使用这个方法能够自旋直到满足某个条件为止
2、SpinWait 是一种值类型,从内存的角度上说,开销很小。,这意味着低级别代码可以利用 SpinWait 而不用担心不必要的分配开销。
3、SpinWait 并不广泛适用于普通应用程序。 在大多数情况下,应使用 .NET Framework 提供的同步类,如  Monitor。 在需要旋转等待的大多数情况下,SpinWait 结构应该优于  Thread.SpinWait() 方法。
4、SpinWait 也会生成时间片,以防等待线程阻止优先级较高的线程或垃圾回收器。就是说旋转一定次数后会让出一下cpu,有sleep、yiled的动作。
需要注意的是:长时间的自旋不是很好的做法,因为自旋会阻塞更高级的线程及其相关的任务,还会阻塞垃圾回收机制。
5、SpinWait 提供每次调用 SpinOnce 前都可以检查的 NextSpinWillYield 属性。如果此属性返回 true,启动自己的等待操作。
6、SpinWait并没有设计为让多个任务或线程并发使用,因此多个任务或线程通过SpinWait方法进行自旋,那么每一个任务或线程都应该使用自己的SpinWait实例。
7、适合与底层代码库,不适合日常使用。

使用案例:

using System.Diagnostics;
using System.Reflection; class Program
{ static SpinLock sl = new();
static void Main(string[] args)
{ Stopwatch stopwatch = new ();
SpinWait sw = new();
//每次自旋的时间
for (int i = 0; i <40; i++)
{ // sw.Reset();// SpinWait内部是 采用count 累计计数的,所以每次使用都要清零
stopwatch.Reset();
stopwatch.Start(); sw.SpinOnce(31); stopwatch.Stop(); Console.WriteLine(stopwatch.ElapsedTicks+sw.NextSpinWillYield.ToString()+"count:"+sw.Count);
} }
}

SpinOnce()执行一次是大概7个时钟周期。第一例外,第一次的时候比较耗时。

案例一、下面的基本示例展示了无锁堆栈中的 SpinWait。 如果需要高性能的线程安全堆栈,请考虑使用 System.Collections.Concurrent.ConcurrentStack<T>

详解:启用3个线程给自定义堆栈LockFreeStack<T>的 字段reeStac 添加数据(0-20)。用到cas 技术保证了线程的同步

LockFreeStack<int> reeStac = new();

for (int i = 1; i <=3; i++)
{
Thread se = new Thread(test);
se.Start();
} void test(){ for (int i = 0; i < 20; i++)
{
reeStac.Push(i); } } public class LockFreeStack<T>
{
private volatile Node m_head; private class Node { public Node Next; public T Value; } public void Push(T item)
{
var spin = new SpinWait();
Node node = new Node { Value = item }, head ;
while (true)
{ head = m_head;
node.Next = head;
Console.WriteLine("Processor:{0},Thread{1},priority:{2} count:{3} ", Thread.GetCurrentProcessorId(), Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.Priority,item );
Node dd = Interlocked.CompareExchange(ref m_head, node, head);//如果相等 就把node赋值给m_head,返回值都是原来的m_head。
if (dd == head) break;//判断是否赋值成功。成功就跳出死循环。
spin.SpinOnce();
Console.WriteLine("Processor:{0},Thread{1},priority:{2} spin.SpinOnce()", Thread.GetCurrentProcessorId(), Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.Priority);
}
} public bool TryPop(out T result)
{
result = default(T);
var spin = new SpinWait(); Node head;
while (true)
{ head = m_head;
if (head == null) return false;
if (Interlocked.CompareExchange(ref m_head, head.Next, head) == head)
{
result = head.Value;
return true;
}
spin.SpinOnce(); //这边使用了spinwait 结构
}
}
}

案例 :用 Thread.SpinWait 模仿SpinWait.Once()的前10次。

using System.Diagnostics;
using System.Reflection; class Program
{ static SpinLock sl = new();
static void Main(string[] args)
{ Type type = typeof(Thread);
BindingFlags flag = BindingFlags.Instance | BindingFlags.NonPublic;
MemberInfo[] inof = type.GetMember("OptimalMaxSpinWaitsPerSpinIteration", BindingFlags.NonPublic); Stopwatch stopwatch = new ();
SpinWait sw = new(); for (int i = 0; i < 20; i++)
{
int ss = 1 << i; stopwatch.Reset();
stopwatch.Start(); Thread.SpinWait(ss);
stopwatch.Stop(); Console.WriteLine(stopwatch.ElapsedTicks+sw.NextSpinWillYield.ToString()+"count:"+ ss);
} }
}

【C# 锁】SpinWait结构 -同步基元|同步原语的更多相关文章

  1. https://github.com/python/cpython/blob/master/Doc/library/contextlib.rst 被同一个线程多次获取的同步基元组件

    # -*- coding: utf-8 -*- import time from threading import Lock, RLock from datetime import datetime ...

  2. 【C# 线程】 atomic action原子操作|primitive(基元、原语)

    概念 原子操作(atomic action):也叫primitive(原语.基元),它是操作系统用语范畴.指由若干条指令组成的,用于完成一定功能的一个过程.  原语是由若干个机器指令构成的完成某种特定 ...

  3. 基元线程同步构造之 Mutes(互斥体)

    互斥体实现了“互相排斥”(mutual exclusion)同步的简单形式(所以名为互斥体(mutex)). 互斥体禁止多个线程同时进入受保护的代码“临界区”(critical section). 因 ...

  4. 【C#进阶系列】28 基元线程同步构造

    多个线程同时访问共享数据时,线程同步能防止数据损坏.之所以要强调同时,是因为线程同步问题实际上就是计时问题. 不需要线程同步是最理想的情况,因为线程同步一般很繁琐,涉及到线程同步锁的获取和释放,容易遗 ...

  5. 【C#】C#线程_基元线程的同步构造

    目录结构: contents structure [+] 简介 为什么需要使用线程同步 线程同步的缺点 基元线程同步 什么是基元线程 基元用户模式构造和内核模式构造的比较 用户模式构造 易变构造(Vo ...

  6. Clr Via C#读书笔记----基元线程同步构造

    线程文章:http://www.cnblogs.com/edisonchou/p/4848131.html 重点在于多个线程同时访问,保持线程的同步. 线程同步的问题: 1,线程同步比较繁琐,而且容易 ...

  7. [.net]基元线程同步构造

    /* 基元线程同步构造 用户模式构造: 易变构造(Volatile Construct) 互锁构造(Interlocked Construct):自旋锁(Spinlock) 乐观锁(Optimisti ...

  8. 基元线程同步构造之waithandle中 waitone使用

    在使用基元线程同步构造中waithandle中waitone方法的讲解: 调用waithandle的waitone方法阻止当前线程(提前是其状态为Nonsignaled,即红灯),直到当前的 Wait ...

  9. mysql对比表结构对比同步,sqlyog架构同步工具

    mysql对比表结构对比同步,sqlyog架构同步工具 对比后的结果示例: 执行后的结果示例: 点击:"另存为(S)" 按钮可以把更新sql导出来.

随机推荐

  1. Node内部架构图

    1.Node内部架构图 先来看一下Node节点的内部实现架构图. 首先最上层入口是Restful风格和javaTcp风格的API入口,RestFul请求映射到处理器RestControl.JavaAp ...

  2. 云图说|DDS读写两步走,带您领略只读节点的风采

    摘要:为了扩展主节点的读请求能力,DDS提供具备独立连接地址的只读节点,适合独立系统直连访问,以缓解大量读请求给主节点造成的压力. 本文分享自华为云社区<[云图说]第235期 DDS读写两步走 ...

  3. 学习AJAX必知必会(3)~自动重启工具nodemon、缓存问题、请求超时和网络异常、取消重复请求

    1.nodemon 自动重启工具(自动重启基于nodejs开发的服务端应用) ■ nodemon 是一个工具,通过在检测到目录中的文件更改时自动重新启动node应用程序来帮助开发node.js. // ...

  4. 花了半年时间,我把Pink老师的HTMLCSS视频课程,整理成了10万字的Markdown笔记!

    说明:本文内容真实!!!不是推广!!! 学习前端的同学应该都或多或少听说过 Pink 老师,我个人觉得 Pink 老师的前端视频教程应该说是目前B站上最好的了,没有之一! Pink老师 HTML CS ...

  5. 重启WAS实例

    /opt/IBM/WebSphere90/AppServer/profiles/appprofile/bin/startServer.sh DASMGW01IDHK-AS01 /opt/IBM/Web ...

  6. 安卓开发常见Bug-setContentView(R.layout.....)报错

    这是安卓开发的常见错误,当你在引用或者复制别人的Layout xml文件时需要在AndroidManifest.xml中添加东西 需要将图中的activity android:name添加进去,否则是 ...

  7. AT2272 [ARC066B] Xor Sum

    我们可以知道异或可以看成不进位的加法,那么我们就可以得到 \(a + b = a\) ^ \(b + ((a \& b) << 1)\),不难发现 \(\frac{v - u}{2 ...

  8. RabbitMQ简介及安装

    AMQP简介 AMQP AMQP(Advanced Message Queuing Protocol,高级消息队列协议)是进程之间传递异步消息的网络协议. AMQP工作过程 发布者(Publisher ...

  9. 浅谈.net core如何使用EFCore为一个上下文注类型注入多个实例用于连接主从数据库

    在很多一主多从数据库的场景下,很多开发同学为了复用DbContext往往采用创建一个包含所有DbSet<Model>父类通过继承派生出Write和ReadOnly类型来实现,其实可以通过命 ...

  10. Spark算子 - aggregate

    释义 将每个partition内元素进行聚合,然后将每个partition的聚合结果进行combine,得到最终聚合结果.最终结果允许跟原始RDD类型不同 方法签名如下: def aggregate[ ...