概念

原子操作(atomic action):也叫primitive(原语、基元),它是操作系统用语范畴。指由若干条指令组成的,用于完成一定功能的一个过程。  原语是由若干个机器指令构成的完成某种特定功能的一段程序,具有不可分割性·即原语的执行必须是连续的,在执行过程中不允许被中断。

操作系统只需在执行以下操作时暂时屏蔽全部中断:测试信号量、更新信号量以及在需要时使某个进程睡眠。由于这些动作只需要几条指令,所以屏蔽中断不会带来什么副作用。如果使用多个CPU,则每个信号量应由一个锁变量进行保护。

在.net中实现原子操作的类是 Interlocked类。CAS在.NET中的实现类是Interlocked

Interlocked类主要方法

interlocked是基于CAS操作

CAS操作基于CPU提供的原子操作指令实现。只能保证共享变量操作的原子:

对于Intel X86处理器,可通过在汇编指令前增加LOCK前缀来锁定系统总线,使系统总线在汇编指令执行时无法访问相应的内存地址。而各个编译器根据这个特点实现了各自的原子操作函数。

CAS是一种有名的无锁算法。无锁编程(指C#代码中不加锁,汇编代码会自动加锁),即不适用锁的情况下实现多线程之间的变量同步,也就是在没有现成被阻塞的情况下实现变量的同步。

CompareExchange(ref a ,b,c):比较吧a,c是否相等,如果相等,则用b替换a的值。
CompareExchange<T>(T, T, T)  比较两个指定的引用类型的实例 T 是否相等,如果相等,则替换第一个。好多原子操作都是基于这个函数实现的。
Decrement(): 安全递减1,相当于 i--
Exchange(): 安全交换数据,相当于 a = 30
Increment() :安全递加1,相当于 i++
Add() :安全相加一个数值,相当于 a = a + 3
Read() : 安全读取数值,相等于int a=b

案例:用5个线程从0数到1亿

using System.Diagnostics;

class Program
{
static long counter = 1;
/// <summary>
/// 开5个线程 从0数到1亿
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
Parallel.Invoke(f1, f1, f1, f1, f1);
// f1();
Console.WriteLine(stopwatch.ElapsedMilliseconds);
Console.WriteLine(counter);
}
static void f1()
{ for (int i = 1; i <= 10_000_000; i++)
{
Interlocked.Increment(ref counter);
// counter++;
}
} }
//5个线程花费时间 1608ms //单线程 125 ms

本以为5个线程会更快,结果还不如一个线程快。这是什么问题???

因为Interlocked.Increment是采用CAS操作

CAS是一种有名的无锁算法。无锁编程(指编程语言方面),即不适用锁的情况下实现多线程之间的变量同步,也就是在没有现成被阻塞的情况下实现变量的同步。

CAS原理

CAS,是“Compare And Swap”的缩写,中文简称就是“比较并替换”。

在这个机制中有三个核心的参数:

  • 主内存中存放的共享变量的值:V(一般情况下这个V是内存的地址值,通过这个地址可以获得内存中的值)
  • 工作内存中共享变量的副本值,也叫预期值:A
  • 需要将共享变量更新到的最新值:B

如上图中,主存中保存V值,线程中要使用V值要先从主存中读取V值到线程的工作内存A中,然后计算后变成B值,最后再把B值写回到内存V值中。多个线程共用V值都是如此操作。CAS的核心是在将B值写入到V之前要比较A值和V值是否相同,如果不相同证明此时V值已经被其他线程改变,重新将V值赋给A(自旋),并重新计算得到B,如果相同,则将B值赋给V。

值得注意的是CAS机制中的这步步骤是原子性的(从cpu指令层面提供的原子操作),所以CAS机制可以解决多线程并发编程对共享变量读写的原子性问题。

CAS的适用场景

读多写少:如果有大量的写操作,CPU开销可能会过大,因为冲突失败后会不断重试(自旋),这个过程中会消耗CPU

cas操作多出比较和写入内存,所以要耗费太多时间了。而单线程不用Interlocked 直接用缓存的数据进行累加,所以单线程更快。

【C# 线程】 atomic action原子操作|primitive(基元、原语)的更多相关文章

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

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

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

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

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

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

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

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

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

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

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

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

  7. .NET 中,编译器直接支持的数据类型称为基元类型(primitive type).基元类型和.NET框架类型(FCL)中的类型有直接的映射关系.

    .NET 中,编译器直接支持的数据类型称为基元类型(primitive type).基元类型和.NET框架类型(FCL)中的类型有直接的映射关系. The primitive types are Bo ...

  8. 解决c#所有单线程单元(STA)线程都应使用泵式等待基元(如 CoWaitForMultipleHandles),并在运行时间很长的操作过程中定期发送消息。 转载

    最近做一个后来程序,启动了事务后有一段操作业务,当运行一段时间后,出现这个异常 CLR 无法从 COM 上下文 0x1b1c38 转换为 COM 上下文 0x1b1da8,这种状态已持续 60 秒.拥 ...

  9. 单线程单元(STA)线程都应使用泵式等待基元

    CLR 无法从 COM 上下文 0x20ad98 转换为 COM 上下文 0x20af08,这种状态已持续 60 秒.拥有目标上下文/单元的线程很有可能执行的是非泵式等待或者在不发送 Windows ...

随机推荐

  1. vue学习5-js表达式

    三目运算符 <!DOCTYPE html> <html lang='en'> <head>  <meta charset='UTF-8'>  <m ...

  2. Go 循环控制

    #### Go 循环控制昨天有工作要忙, 断更一天,不过学习的事情,还是每天要坚持; 我还有头发, 还能学习^_^.***倘若我心中的山水, 你眼中都看到***上一节学习完流程控制,总结一下switc ...

  3. springmvc请求处理过程

    springmvc请求的处理流程 1)发起some.do 2)tomcat(web.xml-----url-pattern知道*.do的请求给DispatcherServlet) 3)Dispatch ...

  4. IDEA包名分层问题

    解决办法: 将默认的"Hide empty Middle Packages"或者"compact middle packages"勾选项去掉,这样就不会把中间空 ...

  5. 集合框架-HashSet集合(无序唯一)

    1 package cn.itcast.p4.hashset.demo; 2 3 import java.util.HashSet; 4 import java.util.Iterator; 5 /* ...

  6. STL中的隐性性能开销与副作用

    1 隐性性能开销 1.1 STL容器的clear的时间复杂度不是O(1) 很多人潜意识认为STL容器中clear()成员函数的时间复杂度为常量时间复杂度O(1).原因是大家觉得对于vector而言,c ...

  7. GDB死锁调试

    1.测试代码 代码中开启两个线程,加锁后轮流输出数据,其中一个线程误将pthread_mutex_unlock(),写成pthread_mutex_lock()代码如下: int g_tickets ...

  8. 聊一聊如何用C#轻松完成一个TCC分布式事务

    背景 银行跨行转账业务是一个典型分布式事务场景,假设 A 需要跨行转账给 B,那么就涉及两个银行的数据,无法通过一个数据库的本地事务保证转账的 ACID ,只能够通过分布式事务来解决. 在 聊一聊如何 ...

  9. AGC005 部分简要题解

    E 首先注意到这个问题事实上非常复杂(两棵树上博弈),基础的转化无效,于是考虑简化情形. 手玩很多样例可以发现,似乎很容易出现无限循环的情况,进一步地,有观察: 若第一棵树上存在相邻点 \(u, v\ ...

  10. Android 存储到SD卡,获取SD的大小及可用空间

    使用Sdcard注意事项:     1.权限问题:             <uses-permission android:name="android.permission.WRIT ...