问题:多个Task需要操作共享变量时,会有资源竞争的问题,例如下段代码示例:
void Main() { int shared = 0; Task[] tasks = new Task[10]; for (int i = 0;i < 10; i++) { //create a new task tasks[i] = new Task(() => { // entera loop for 1000 balance updates for(int j = 0;j < 1000; j++) { //update the balance shared++; } }); // startthe new task tasks[i].Start(); } Task.WaitAll(tasks); Console.WriteLine(string.Format("shared : {0}",shared)); Console.ReadLine(); }
期望结果:10000,可实际结果每次都不一样(第一次为8093)。
即每个Task独享一个变量,最后合并结果:
void Main() { Task<int>[] tasks = new Task<int>[10]; for(int i = 0;i < 10; i++) { //create a new task var taskVal = 0; tasks[i] = new Task<int>((v)=> { int val = int.Parse(v.ToString()); // enter a loop for 1000 balance updates for(int j = 0;j < 1000; j++) { //update the balance val++; } return val; },taskVal); // start the new task tasks[i].Start(); } int result = 0; for(int i = 0;i < 10; i++) { result+= tasks[i].Result; } Console.WriteLine(string.Format("result : {0}",result)); Console.ReadLine(); }
每个线程独享1个变量,进行计算返回结果,最后合并结果(在tasks[i].Result时,Task会阻塞)。
相信读者对锁已经不再陌生,它在解决资源竞争问题时是经常出现的,思路就是设定关键区域,把那部分操作变成同步的(同时只允许一个线程操作)。
lock :重量级锁,会带来线程切换的开销。
lock (lockObj) { ...critical region code... }
等价于:
bool lockAcquired; try { Monitor.Enter(lockObj, reflockAcquired); ...critical region code... } finally { if (lockAcquired)Monitor.Exit(lockObj); }
System.Threading.Interlocked:轻量级锁,会调用底层硬件特性进行优化,对于只需要操作整数++或整数--的情况很适用。常见方法:
● Add:整数加和
● Exchange:赋值
● Increment:递增
● Decrement :递减
CompareExchange<T>:比较两个数,相等则赋值
System.Threading.Mutex:可用于完成跨进程资源同步(使用命名的mutex)。
使用Synchronize特性:声明式加锁,会对类中的所有成员,方法实现锁机制,谨慎使用。
SpinLock:轻量级锁。通常对于短时间内(例如在指令级别)资源同步的情况,性能会高于monitor或SlimReaderWriterLock。
SlimReaderWriterLock:轻量级锁。优势在于读写锁分离。避免在读锁中打开写锁,会造成死锁。即便可以通过EnterUpgradeableReadLock来实现这种场景,仍然不推荐这样做。
在多线程场景中,没有并发集合之前,使用集合时需要手动加锁来解决资源共享的问题,以下四种并发集合可以使我们从这种问题中release出来,在多线程场景中可以直接拿来用:
ConcurrentQueue,ConcurrentStack,ConcurrentBag,ConcurrentDictionary。
原文:http://blog.csdn.net/lan_liang/article/details/45271541