大家都晓得.NET中线程同步有以下几种方式:
临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)、事件(Event)
1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占。
2、互斥量:采用互斥对象机制。 只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问。互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享
3、信号量:它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目
4、事 件: 通过通知操作的方式来保持线程的同步,还可以方便实现对多个线程的优先级比较的操作
临界区
Interlocked:为多个线程共享的变量提供原子操作
此类的方法可以防止可能在下列情况发生的错误:计划程序在某个线程正在更新可由其他线程访问的变量时切换上下文;或者当两个线程在不同的处理器上并发执行时。 此类的成员不引发异常。那么问题来了,为什么是原子级?为什么共有访问权限的实例变量会在线程上下文切换的时候丢失结果?
在大多数计算机上,增加变量操作不是一个原子操作,需要执行下列步骤:
1)将实例变量中的值加载到寄存器中
2)改变该值
3)在实例变量中存储该值
所以当线程A执行了前两步之后,线程B(可能是单核的cpu上下文切换,也可能是多核中的其他线程)对同一个实例变量执行了三部曲,那么A在寄存器中的操作就会被覆盖,值就丢失了。如果换成Interlocked.Increment/Decrement,按照我的猜测,它对于值的原子操作是一种不可打断性的,类似锁。windows编程中也提供有类似的Interlocked关键字的函数,同样具有原子性操作特性,但它的实现方式是基于“锁定内存”以达到隔离其他线程访问该内存的目的,详见《windows核心编程》。
Interlocked.Exchange(ref a, 0),交换a和0,并返回a。
Interlocked.CompareExchange(ref isSington, 1, 0),目标操作数(第1参数所指向的内存中的数)与一个值(第3参数)比较,如果相等,则用另一个值(第2参数)与目标操作数(第1参数所指向的内存中的数)交换,此方法用处比较多,最常见的是单例模式。
public class ConsumerCancel { private int isSington = 0; public bool ConsumeAsync(Action<IMessage> action, int backThreadCount) { try { if (Interlocked.CompareExchange(ref isSington, 1, 0) != 0) return true;//保证单例,已运行的不再运行 } catch (Exception ex) { //xxxx } } }
下面这个是Interlocked类的源码
namespace System.Threading { using System; using System.Security.Permissions; using System.Runtime.CompilerServices; using System.Runtime.ConstrainedExecution; using System.Runtime.Versioning; using System.Runtime; // After much discussion, we decided the Interlocked class doesn't need // any HPA's for synchronization or external threading. They hurt C#'s // codegen for the yield keyword, and arguably they didn't protect much. // Instead, they penalized people (and compilers) for writing threadsafe // code. public static class Interlocked { /****************************** * Increment * Implemented: int * long *****************************/ [ResourceExposure(ResourceScope.None)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] public static int Increment(ref int location) { return Add(ref location, 1); } [ResourceExposure(ResourceScope.None)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] public static long Increment(ref long location) { return Add(ref location, 1); } /****************************** * Decrement * Implemented: int * long *****************************/ [ResourceExposure(ResourceScope.None)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] public static int Decrement(ref int location) { return Add(ref location, -1); } [ResourceExposure(ResourceScope.None)] public static long Decrement(ref long location) { return Add(ref location, -1); } /****************************** * Exchange * Implemented: int * long * float * double * Object * IntPtr *****************************/ [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [System.Security.SecuritySafeCritical] public static extern int Exchange(ref int location1, int value); [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [System.Security.SecuritySafeCritical] public static extern long Exchange(ref long location1, long value); [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [System.Security.SecuritySafeCritical] public static extern float Exchange(ref float location1, float value); [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [System.Security.SecuritySafeCritical] public static extern double Exchange(ref double location1, double value); [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [System.Security.SecuritySafeCritical] public static extern Object Exchange(ref Object location1, Object value); [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [System.Security.SecuritySafeCritical] public static extern IntPtr Exchange(ref IntPtr location1, IntPtr value); [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [System.Runtime.InteropServices.ComVisible(false)] [System.Security.SecuritySafeCritical] public static T Exchange<T>(ref T location1, T value) where T : class { _Exchange(__makeref(location1), __makeref(value)); //Since value is a local we use trash its data on return // The Exchange replaces the data with new data // so after the return "value" contains the original location1 //See ExchangeGeneric for more details return value; } [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [System.Security.SecuritySafeCritical] private static extern void _Exchange(TypedReference location1, TypedReference value); /****************************** * CompareExchange * Implemented: int * long * float * double * Object * IntPtr *****************************/ [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [System.Security.SecuritySafeCritical] public static extern int CompareExchange(ref int location1, int value, int comparand); [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [System.Security.SecuritySafeCritical] public static extern long CompareExchange(ref long location1, long value, long comparand); [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [System.Security.SecuritySafeCritical] public static extern float CompareExchange(ref float location1, float value, float comparand); [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [System.Security.SecuritySafeCritical] public static extern double CompareExchange(ref double location1, double value, double comparand); [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [System.Security.SecuritySafeCritical] public static extern Object CompareExchange(ref Object location1, Object value, Object comparand); [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [System.Security.SecuritySafeCritical] public static extern IntPtr CompareExchange(ref IntPtr location1, IntPtr value, IntPtr comparand); /***************************************************************** * CompareExchange<T> * * Notice how CompareExchange<T>() uses the __makeref keyword * to create two TypedReferences before calling _CompareExchange(). * This is horribly slow. Ideally we would like CompareExchange<T>() * to simply call CompareExchange(ref Object, Object, Object); * however, this would require casting a "ref T" into a "ref Object", * which is not legal in C#. * * Thus we opted to cheat, and hacked to JIT so that when it reads * the method body for CompareExchange<T>() it gets back the * following IL: * * ldarg.0 * ldarg.1 * ldarg.2 * call System.Threading.Interlocked::CompareExchange(ref Object, Object, Object) * ret * * See getILIntrinsicImplementationForInterlocked() in VM\JitInterface.cpp * for details. *****************************************************************/ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [System.Runtime.InteropServices.ComVisible(false)] [System.Security.SecuritySafeCritical] public static T CompareExchange<T>(ref T location1, T value, T comparand) where T : class { // _CompareExchange() passes back the value read from location1 via local named 'value' _CompareExchange(__makeref(location1), __makeref(value), comparand); return value; } [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [System.Security.SecuritySafeCritical] private static extern void _CompareExchange(TypedReference location1, TypedReference value, Object comparand); // BCL-internal overload that returns success via a ref bool param, useful for reliable spin locks. [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [System.Security.SecuritySafeCritical] internal static extern int CompareExchange(ref int location1, int value, int comparand, ref bool succeeded); /****************************** * Add * Implemented: int * long *****************************/ [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] internal static extern int ExchangeAdd(ref int location1, int value); [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static extern long ExchangeAdd(ref long location1, long value); [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] public static int Add(ref int location1, int value) { return ExchangeAdd(ref location1, value) + value; } [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] public static long Add(ref long location1, long value) { return ExchangeAdd(ref location1, value) + value; } /****************************** * Read *****************************/ public static long Read(ref long location) { return Interlocked.CompareExchange(ref location,0,0); } public static void MemoryBarrier() { Thread.MemoryBarrier(); } } }
信号量,随笔中有专门介绍的,不再多说了。
案例:购买火车票
using System; using System.Threading; namespace MutiThreadSample.ThreadSynchronization { /// <summary> /// 案例:支付流程 /// 如超市、药店、火车票等,都有限定的几个窗口进行结算,只有有窗口空闲,才能进行结算。 /// 我们就用多线程来模拟结算过程 /// </summary> class PaymentWithSemaphore { /// <summary> /// 声明收银员总数为3个,但是当前空闲的个数为0,可能还没开始上班。 /// </summary> private static Semaphore IdleCashiers = new Semaphore(0, 3); /// <summary> /// 测试支付过程 /// </summary> public static void TestPay() { ParameterizedThreadStart start = new ParameterizedThreadStart(Pay); //假设同时有5个人来买票 for (int i = 0; i < 5; i++) { Thread thread = new Thread(start); thread.Start(i); } //主线程等待,让所有的的线程都激活 Thread.Sleep(1000); //释放信号量,2个收银员开始上班了或者有两个空闲出来了 IdleCashiers.Release(2); } /// <summary> /// /// </summary> /// <param name="obj"></param> public static void Pay(object obj) { Console.WriteLine("Thread {0} begins and waits for the semaphore.", obj); IdleCashiers.WaitOne(); Console.WriteLine("Thread {0} starts to Pay.",obj); //结算 Thread.Sleep(2000); Console.WriteLine("Thread {0}: The payment has been finished.",obj); Console.WriteLine("Thread {0}: Release the semaphore.", obj); IdleCashiers.Release(); } } } 购买火车票
互斥量,最常见的莫过于lock(object)了,不多说。
public void Function() { System.Object locker= new System.Object(); lock(locker) { // Access thread-sensitive resources. } }
事件,同步事件和等待句柄。这里引用”停留的风“的描述和用例。
使用锁或监视器对于防止同时执行区分线程的代码块很有用,但是这些构造不允许一个线程向另一个线程传达事件。这需要“同步事件”,它是有两个状态(终止和非终止)的对象,可以用来激活和挂起线程。让线程等待非终止的同步事件可以将线程挂起,将事件状态更改为终止可以将线程激活。如果线程试图等待已经终止的事件,则线程将继续执行,而不会延迟。同步事件有两种:AutoResetEvent和ManualResetEvent。它们之间唯一的不同在于,无论何时,只要 AutoResetEvent 激活线程,它的状态将自动从终止变为非终止。相反,ManualResetEvent 允许它的终止状态激活任意多个线程,只有当它的 Reset 方法被调用时才还原到非终止状态。
等待句柄,可以通过调用一种等待方法,如 WaitOne、WaitAny 或 WaitAll,让线程等待事件。System.Threading.WaitHandle.WaitOne 使线程一直等待,直到单个事件变为终止状态;System.Threading.WaitHandle.WaitAny 阻止线程,直到一个或多个指示的事件变为终止状态;System.Threading.WaitHandle.WaitAll 阻止线程,直到所有指示的事件都变为终止状态。当调用事件的 Set 方法时,事件将变为终止状态。
AutoResetEvent允许线程通过发信号互相通信。通常当线程需要独占访问资源时使用该类。线程通过调用AutoResetEvent上的WaitOne来等待信号。如果AutoResetEvent为非终止状态,则线程会被阻止,并等待当前控制资源的线程通过调用Set来通知资源可用。调用Set向AutoResetEvent发信号以释放等待线程。AutoResetEvent将保持终止状态,直到一个正在等待的线程被释放,然后自动返回非终止状态。如果没有任何线程在等待,则状态将无限期地保持为终止状态。如果当AutoResetEvent为终止状态时线程调用WaitOne,则线程不会被阻止。AutoResetEvent将立即释放线程并返回到非终止状态。可以通过将一个布尔值传递给构造函数来控制 AutoResetEvent的初始状态:如果初始状态为终止状态,则为 true;否则为 false。AutoResetEvent也可以同 staticWaitAll 和 WaitAny 方法一起使用。
用例:我们来做饭,做饭呢,需要一菜、一粥。今天我们吃鱼。
熬粥和做鱼,是比较复杂的工作流程,
做粥:选材、淘米、熬制
做鱼:洗鱼、切鱼、腌制、烹调
为了提高效率,我们用两个线程来准备这顿饭,但是,现在只有一口锅,只能等一个做完之后,另一个才能进行最后的烹调。
using System; using System.Threading; namespace MutiThreadSample.ThreadSynchronization { /// <summary> /// 案例:做饭 /// 今天的Dinner准备吃鱼,还要熬粥 /// 熬粥和做鱼,是比较复杂的工作流程, /// 做粥:选材、淘米、熬制 /// 做鱼:洗鱼、切鱼、腌制、烹调 /// 我们用两个线程来准备这顿饭 /// 但是,现在只有一口锅,只能等一个做完之后,另一个才能进行最后的烹调 /// </summary> class CookResetEvent { /// <summary> /// /// </summary> private AutoResetEvent resetEvent = new AutoResetEvent(false); /// <summary> /// 做饭 /// </summary> public void Cook() { Thread porridgeThread = new Thread(new ThreadStart(Porridge)); porridgeThread.Name = "Porridge"; porridgeThread.Start(); Thread makeFishThread = new Thread(new ThreadStart(MakeFish)); makeFishThread.Name = "MakeFish"; makeFishThread.Start(); //等待5秒 Thread.Sleep(5000); resetEvent.Reset(); } /// <summary> /// 熬粥 /// </summary> public void Porridge() { //选材 Console.WriteLine("Thread:{0},开始选材", Thread.CurrentThread.Name); //淘米 Console.WriteLine("Thread:{0},开始淘米", Thread.CurrentThread.Name); //熬制 Console.WriteLine("Thread:{0},开始熬制,需要2秒钟", Thread.CurrentThread.Name); //需要2秒钟 Thread.Sleep(2000); Console.WriteLine("Thread:{0},粥已经做好,锅闲了", Thread.CurrentThread.Name); resetEvent.Set(); } /// <summary> /// 做鱼 /// </summary> public void MakeFish() { //洗鱼 Console.WriteLine("Thread:{0},开始洗鱼",Thread.CurrentThread.Name); //腌制 Console.WriteLine("Thread:{0},开始腌制", Thread.CurrentThread.Name); //等待锅空闲出来 resetEvent.WaitOne(); //烹调 Console.WriteLine("Thread:{0},终于有锅了", Thread.CurrentThread.Name); Console.WriteLine("Thread:{0},开始做鱼,需要5秒钟", Thread.CurrentThread.Name); Thread.Sleep(5000); Console.WriteLine("Thread:{0},鱼做好了,好香", Thread.CurrentThread.Name); resetEvent.Set(); } } }
其他方式,引用来自”停留的风“
1.Mutex对象
mutex与监视器类似;它防止多个线程在某一时间同时执行某个代码块。事实上,名称“mutex”是术语“互相排斥 (mutually exclusive)”的简写形式。然而与监视器不同的是,mutex可以用来使跨进程的线程同步。mutex 由Mutex类表示。当用于进程间同步时,mutex称为“命名 mutex”,因为它将用于另一个应用程序,因此它不能通过全局变量或静态变量共享。必须给它指定一个名称,才能使两个应用程序访问同一个mutex对象。
尽管mutex可以用于进程内的线程同步,但是使用Monitor通常更为可取,因为监视器是专门为.NETFramework而设计的,因而它可以更好地利用资源。相比之下,Mutex类是Win32构造的包装。尽管mutex比监视器更为强大,但是相对于Monitor类,它所需要的互操作转换更消耗计算资源。
本地mutex和系统mutex
Mutex分两种类型:本地mutex和命名系统mutex。如果使用接受名称的构造函数创建了Mutex对象,那么该对象将与具有该名称的操作系统对象相关联。命名的系统mutex在整个操作系统中都可见,并且可用于同步进程活动。您可以创建多个Mutex对象来表示同一命名系统 mutex,而且您可以使用OpenExisting方法打开现有的命名系统mutex。
本地 mutex 仅存在于进程当中。 进程中引用本地 Mutex 对象的任意线程都可以使用本地 mutex。 每个 Mutex 对象都是一个单独的本地 mutex。
在本地Mutex中,用法与Monitor基本一致。
/// <summary> /// mutex对象 /// </summary> private static Mutex mutex = new Mutex(); /// <summary> /// 使用打印机进行打印 /// </summary> private static void UsePrinterWithMutex() { mutex.WaitOne(); try { Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name); //模拟打印操作 Thread.Sleep(500); Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name); } finally { mutex.ReleaseMutex(); } }
多线程调用:
/// <summary> /// 测试 /// </summary> public static void TestPrint() { Thread thread; Random random = new Random(); for (int i = 0; i < ComputerCount; i++) { thread = new Thread(MyThreadProc); thread.Name = string.Format("Thread{0}", i); Thread.Sleep(random.Next(3)); thread.Start(); } } /// <summary> /// 线程执行操作 /// </summary> private static void MyThreadProc() { //使用打印机进行打印 //UsePrinter(); //monitor同步 //UsePrinterWithMonitor(); //用Mutex同步 UsePrinterWithMutex(); //当前线程等待1秒 Thread.Sleep(1000); }
2.读取器/编写器锁
ReaderWriterLockSlim类允许多个线程同时读取一个资源,但在向该资源写入时要求线程等待以获得独占锁。可以在应用程序中使用ReaderWriterLockSlim,以便在访问一个共享资源的线程之间提供协调同步。获得的锁是针对ReaderWriterLockSlim本身的。设计您应用程序的结构,让读取和写入操作的时间尽可能最短。因为写入锁是排他的,所以长时间的写入操作会直接影响吞吐量。 长时间的读取操作会阻止处于等待状态的编写器,并且,如果至少有一个线程在等待写入访问,则请求读取访问的线程也将被阻止。
案例:构造一个线程安全的缓存
using System; using System.Threading; using System.Collections.Generic; namespace MutiThreadSample.ThreadSynchronization { /// <summary> /// 同步Cache /// </summary> public class SynchronizedCache { private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim(); private Dictionary<int, string> innerCache = new Dictionary<int, string>(); /// <summary> /// 读取 /// </summary> /// <param name="key"></param> /// <returns></returns> public string Read(int key) { cacheLock.EnterReadLock(); try { return innerCache[key]; } finally { cacheLock.ExitReadLock(); } } /// <summary> /// 添加项 /// </summary> /// <param name="key"></param> /// <param name="value"></param> public void Add(int key, string value) { cacheLock.EnterWriteLock(); try { innerCache.Add(key, value); } finally { cacheLock.ExitWriteLock(); } } /// <summary> /// 添加项,有超时限制 /// </summary> /// <param name="key"></param> /// <param name="value"></param> /// <param name="timeout"></param> /// <returns></returns> public bool AddWithTimeout(int key, string value, int timeout) { if (cacheLock.TryEnterWriteLock(timeout)) { try { innerCache.Add(key, value); } finally { cacheLock.ExitWriteLock(); } return true; } else { return false; } } /// <summary> /// 添加或者更新 /// </summary> /// <param name="key"></param> /// <param name="value"></param> /// <returns></returns> public AddOrUpdateStatus AddOrUpdate(int key, string value) { cacheLock.EnterUpgradeableReadLock(); try { string result = null; if (innerCache.TryGetValue(key, out result)) { if (result == value) { return AddOrUpdateStatus.Unchanged; } else { cacheLock.EnterWriteLock(); try { innerCache[key] = value; } finally { cacheLock.ExitWriteLock(); } return AddOrUpdateStatus.Updated; } } else { cacheLock.EnterWriteLock(); try { innerCache.Add(key, value); } finally { cacheLock.ExitWriteLock(); } return AddOrUpdateStatus.Added; } } finally { cacheLock.ExitUpgradeableReadLock(); } } /// <summary> /// 删除项 /// </summary> /// <param name="key"></param> public void Delete(int key) { cacheLock.EnterWriteLock(); try { innerCache.Remove(key); } finally { cacheLock.ExitWriteLock(); } } /// <summary> /// /// </summary> public enum AddOrUpdateStatus { Added, Updated, Unchanged }; } }
3.障碍(Barrier)4.0后技术
使多个任务能够采用并行方式依据某种算法在多个阶段中协同工作。通过在一系列阶段间移动来协作完成一组任务,此时该组中的每个任务发信号指出它已经到达指定阶段的 Barrier 并且暗中等待其他任务到达。 相同的 Barrier 可用于多个阶段。
4.SpinLock(4.0后)
SpinLock结构是一个低级别的互斥同步基元,它在等待获取锁时进行旋转。在多核计算机上,当等待时间预计较短且极少出现争用情况时,SpinLock的性能将高于其他类型的锁。不过,我们建议您仅在通过分析确定System.Threading.Monitor 方法或 Interlocked 方法显著降低了程序的性能时使用 SpinLock。
即使SpinLock未获取锁,它也会产生线程的时间片。它这样做是为了避免线程优先级别反转,并使垃圾回收器能够继续执行。在使用SpinLock时,请确保任何线程持有锁的时间不会超过一个非常短的时间段,并确保任何线程在持有锁时不会阻塞。
由于 SpinLock 是一个值类型,因此,如果您希望两个副本都引用同一个锁,则必须通过引用显式传递该锁。
using System; using System.Text; using System.Threading; using System.Threading.Tasks; namespace MutiThreadSample.ThreadSynchronization { class SpinLockSample { public static void Test() { SpinLock sLock = new SpinLock(); StringBuilder sb = new StringBuilder(); Action action = () => { bool gotLock = false; for (int i = 0; i < 100; i++) { gotLock = false; try { sLock.Enter(ref gotLock); sb.Append(i.ToString()); } finally { //真正获取之后,才释放 if (gotLock) sLock.Exit(); } } }; //多线程调用action Parallel.Invoke(action, action, action); Console.WriteLine("输出:{0}",sb.ToString()); } } }
5.SpinWait(4.0后)
System.Threading.SpinWait是一个轻量同步类型,可以在低级别方案中使用它来避免内核事件所需的高开销的上下文切换和内核转换。在多核计算机上,当预计资源不会保留很长一段时间时,如果让等待线程以用户模式旋转数十或数百个周期,然后重新尝试获取资源,则效率会更高。 如果在旋转后资源变为可用的,则可以节省数千个周期。 如果资源仍然不可用,则只花费了少量周期,并且仍然可以进行基于内核的等待。 这一旋转-等待的组合有时称为“两阶段等待操作”。
下面的基本示例采用微软案例:无锁堆栈
using System; using System.Threading; namespace MutiThreadSample.ThreadSynchronization { 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; if (Interlocked.CompareExchange(ref m_head, node, head) == head) break; spin.SpinOnce(); } } 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(); } } } }
以上的介绍中我也只是用过一小部分,有些场景没有遇到过,可能无法体会甚至理解,收藏起来仅供学习研究。