TPL Part3 -- 数据共享

来源:互联网 发布:网络在线咨询图片 编辑:程序博客网 时间:2024/06/08 12:36

问题:多个Task需要操作共享变量时,会有资源竞争的问题,例如下段代码示例:

void Main(){ int shared = 0; Task[] tasks = new Task[10];for (int i = 0;i < 10; i++) {//create a new tasktasks[i] = new Task(() => {// entera loop for 1000 balance updatesfor(int j = 0;j < 1000; j++) {//update the balanceshared++;}});// startthe new tasktasks[i].Start();} Task.WaitAll(tasks); Console.WriteLine(string.Format("shared : {0}",shared));Console.ReadLine();}


期望结果:10000,可实际结果每次都不一样(第一次为8093)。

解决方案1:隔离

即每个Task独享一个变量,最后合并结果:

void Main(){ Task<int>[] tasks = new Task<int>[10];for(int i = 0;i < 10; i++) {//create a new taskvar taskVal = 0;tasks[i] = new Task<int>((v)=> {int val = int.Parse(v.ToString());// enter a loop for 1000 balance updatesfor(int j = 0;j < 1000; j++) {//update the balanceval++;}return val;},taskVal);// start the new tasktasks[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会阻塞)。

方案2:加锁

相信读者对锁已经不再陌生,它在解决资源竞争问题时是经常出现的,思路就是设定关键区域,把那部分操作变成同步的(同时只允许一个线程操作)。

锁的种类

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。

 

1 0
原创粉丝点击