线程队列--{ConcurrentQueue}
一 什么是原子操作
所谓原子访问,指的是一个线程在访问某个资源的同时能够保证没有其他线程会在同一时刻访问同一资源。Interlocked系列函数提供了这样的操作。所有这些函数会以原子方式来操控一个值。
Interlocked函数的工作原理取决于代码运行的CPU平台,如果是x86系列CPU,那么Interlocked函数会在总线上维持一个硬件信号,这个信号会阻止其他CPU访问同一个内存地址。我们必须确保传给这些函数的变量地址是经过对齐的,否则这些函数可能会失败。
在进行多线程编程的时候特别重要的一点就是多线程的同步,什么是同步呢?字面意思就是使多个不在同一线程执行的代码统一到一个线程中执行,但是对执行中的线程过程却无法控制,这就造成了多个线程可能同时操作同一个变量,于是就出现了得到的结果不是想要的结果,为了避免这个情况,我们常用的方法是加锁例如locked,但是为了一个很简单的操作例如a++这样的操作频繁的locked对性能的影响得不偿失,所以就需要用到InterLocked这样的原子操作,因为原子操作是基于硬件层面的非阻塞的操作,所以性能非常的好。
一下是关于Interlocked的原子操作
static void Main(string[] args) { // Construct a ConcurrentQueue. ConcurrentQueue<int> cq = new ConcurrentQueue<int>(); // Populate the queue. for (int i = 0; i < 10000; i++) { cq.Enqueue(i); } // Peek at the first element. int result; if (!cq.TryPeek(out result)) { Console.WriteLine("CQ: TryPeek failed when it should have succeeded"); } else if (result != 0) { Console.WriteLine("CQ: Expected TryPeek result of 0, got {0}", result); } int outerSum = 0; // An action to consume the ConcurrentQueue. Action action = () => { int localSum = 0; int localValue; while (cq.TryDequeue(out localValue)) localSum += localValue; Interlocked.Add(ref outerSum, localSum); }; // Start 4 concurrent consuming actions. Parallel.Invoke(action, action, action, action); Console.WriteLine("outerSum = {0}, should be 49995000", outerSum); Console.ReadKey(); }
Interlocked.Add(ref outerSum, localSum);这一句实现的就是outerSum+= localSum【并且可以理解为多个线程同时操作此数据,无影响,实际是硬件操作,所以无影响】
// 摘要: // 对两个 32 位整数进行求和并用和替换第一个整数,上述操作作为一个原子操作完成。 // // 参数: // location1: // 一个变量,包含要添加的第一个值。两个值的和存储在 location1 中。 // // value: // 要添加到整数中的 location1 位置的值。 // // 返回结果: // 存储在 location1 处的新值。 // // 异常: // T:System.NullReferenceException: // location1 的地址为空指针。 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] public static int Add(ref int location1, int value);
下面的代码示例演示线程安全资源锁定机制。
using System; using System.Threading; namespace InterlockedExchange_Example { class MyInterlockedExchangeExampleClass { //0 for false, 1 for true. private static int usingResource = 0; private const int numThreadIterations = 5; private const int numThreads = 10; static void Main() { Thread myThread; Random rnd = new Random(); for(int i = 0; i < numThreads; i++) { myThread = new Thread(new ThreadStart(MyThreadProc)); myThread.Name = String.Format("Thread{0}", i + 1); //Wait a random amount of time before starting next thread. Thread.Sleep(rnd.Next(0, 1000)); myThread.Start(); } } private static void MyThreadProc() { for(int i = 0; i < numThreadIterations; i++) { UseResource(); //Wait 1 second before next attempt. Thread.Sleep(1000); } } //A simple method that denies reentrancy. static bool UseResource() { //0 indicates that the method is not in use. if(0 == Interlocked.Exchange(ref usingResource, 1)) { Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name); //Code to access a resource that is not thread safe would go here. //Simulate some work Thread.Sleep(500); Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name); //Release the lock Interlocked.Exchange(ref usingResource, 0); return true; } else { Console.WriteLine(" {0} was denied the lock", Thread.CurrentThread.Name); return false; } } } }
此类的方法可帮助防止在以下情况下发生的错误:在以下情况下发生:计划程序在以下情况下切换上下文:当线程正在更新可被其他线程访问的变量时,或当两个线程同时在不同的处理器上执行时。 此类的成员不会引发异常。
Increment和 Decrement 方法递增或递减变量,并在单个操作中存储生成的值。 在大多数计算机上,递增变量不是原子操作,需要执行以下步骤:
-
将实例变量中的值加载到寄存器中。
-
递增或减小值。
-
将值存储在实例变量中。
如果不使用 Increment 和,则在 Decrement 执行前两个步骤后,线程可以被抢占。 然后,另一个线程可以执行所有三个步骤。 当第一个线程继续执行时,它将覆盖实例变量中的值,并且由第二个线程执行的增量或减量的影响将丢失。
Add方法以原子方式将整数值添加到整数变量中,并返回变量的新值。
Exchange方法以原子方式交换指定变量的值。 CompareExchange方法组合了两个操作:比较两个值,并根据比较结果将第三个值存储在一个变量中。 比较和交换操作以原子操作的方式执行。
确保对共享变量的任何写入或读取访问都是原子的。 否则,数据可能已损坏,或者加载的值可能不正确。
二 ConcurrentQueue<T>