【C# 线程】interLocked锁
overview
同步基元分为用户模式和内核模式
用户模式:Iterlocked.Exchange(互锁)、SpinLocked(自旋锁)、易变构造(volatile关键字、
volatile
类、Thread.VolatitleRead|Thread.VolatitleWrite
)、MemoryBarrier。
.net中的System.Threading命名空间的Interlocked类可以为多个线程共享的变量提供原子操作。
经验显示,那些需要在多线程下被保护的资源通常是整型的,而这些被共享的整型值最常见的操作就是增加、减少。Interlocked类提供了一个专门的机制用于完成这些特定的操作。
Iterlocked.Exchange()
不只是原子的,它还具有内存可见性。
所有 方法都生成一个完整的栅栏。因此,您通过访问的字段不需要额外的栅栏。
Interlocked 轻量级锁
Interlocked.Increment(ref value) 数值加一(原子性操作)
Interlocked.Decrement(ref value) 数值减一(原子性操作)
Interlocked.Exchange(ref value1, value2) 交换:把值2赋给值1;返回新值
Interlocked.CompareExchange(ref value1, value2, value3) 实现比较和交换两种功能:值1和值3比较,如果相同,把值2给值1,不相同则不作任何操作;返回原值(多用于判断条件)(示例3中会用到)
Interlocked.MemoryBarrier :按如下方式同步内存存取:执行当前线程的处理器在对指令重新排序时,不能采用先执行 MemoryBarrier() 调用之后的内存存取,再执行 MemoryBarrier() 调用之前的内存存取的方式。Thread.MemoryBarrier 就是包装了它。
个人补充:值刷新当前执行线程的cpu上的store buffer和Invalidate queue都运行完成了.
Interlocked.MemoryBarrierProcessWide:内部执行FlushProcessWriteBuffers函数()。该函数功能刷新正在运行当前进程所有线程的每个处理器的写入队列(store buffer)。该函数为属于当前进程关联一部分的所有处理器生成一个处理器间中断 (IPI)。它保证了在一个处理器上对另一个处理器执行的写入操作的可见性。
个人补充:将当前进程下所有线程所在的cpu上的store buffer和Invalidate queue都运行完成了,所以非常耗费时间 。
注意不要传入 volatile类型。
Interlocked.Add 方法:以原子操作的形式,添加两个整数并用两者的和替换第一个整数。
Add(Int32, Int32)|Add(Int64, Int64)Add|(UInt32, UInt32)|Add(UInt64, UInt64)
对两个 32 位整数进行求和并用和替换第一个整数,上述操作作为一个原子操作完成。
And(Int32, Int32)方法:对两个 32 位带符号整数进行按位“与”运算,并用结果替换第一个整数,上述操作作为一个原子操作完成。
Add(Int32, Int32)|Add(Int64, Int64)Add|(UInt32, UInt32)|Add(UInt64, UInt64)
对两个 32 位带符号整数进行按位“与”运算,并用结果替换第一个整数,上述操作作为一个原子操作完成。
Interlocked.Or 方法:对两个 32 位带符号整数进行按位“或”运算,并用结果替换第一个整数,上述操作作为一个原子操作完成。
Add(Int32, Int32)|Add(Int64, Int64)Add|(UInt32, UInt32)|Add(UInt64, UInt64)
对两个 32 位带符号整数进行按位“或”运算,并用结果替换第一个整数,上述操作作为一个原子操作完成。
Interlocked.Read 方法 :返回一个以原子操作形式加载的 64 位值。
Read(Int64) |Read(UInt64)
返回一个以原子操作形式加载的 64 位值。
MemoryBarrierProcessWide方法与“普通”MemoryBarrier方法的区别如下:
正常的memory barrier可以确保当前CPU的读写不能跨越barrier。进程级内存屏障确保了进程中使用的任何CPU的任何读或写操作都不能跨越这个屏障。
如果每个访问数据的线程都使用barrier,那么正常的内存barrier允许合理的共享访问。进程级内存屏障迫使其他cpu与进程内存同步(例如,刷新写缓冲区和同步读缓冲区)。这允许在某些线程上进行非连锁操作,并且仍然有合理的共享访问。
正常的内存屏障的开销很小;正常的连锁操作可能花费不到100个周期。进程级内存屏障非常昂贵。它必须迫使进程中的每个CPU做一些事情,可能要花费数千个周期。
MemoryBarrierProcessWide方法还受到无锁编程的所有微妙之处的影响。然而,当您实际需要调用它时,这个方法可能非常有用,这种情况应该很少见。
该方法封装了对Windows上的FlushProcessWriteBuffers和Linux上的sys_membarrier的调用。
案例来自:
这个案例主要考验对 volatile关键字和内存屏障的理解。
该案例会出乎意料的输出0,0 。为了避免该情况必须使用全内存屏障。
该例子来自:volatile的内存屏障的坑
using System; using System.Threading; using System.Threading.Tasks; namespace MemoryBarriers { class Program { static volatile int x, y, a, b; static void Main() { while (true) { var t1 = Task.Run(Test1); var t2 = Task.Run(Test2); Task.WaitAll(t1, t2); if (a == 0 && b == 0) { Console.WriteLine("{0}, {1}", a, b); } x = y = a = b = 0; } } static void Test1() { x = 1; //方案一 Interlocked.MemoryBarrier(); //方案二 Interlocked.MemoryBarrierProcessWide(); a = y; } static void Test2() { y = 1; //方案一 Interlocked.MemoryBarrier(); b = x; } } }