上一篇基元线程同步——基础,非阻塞同步(VolatileRead,VolatileWrite,volatile,Interlocked) 已经对Interlocked类做了比较详细的分析,这一篇是对Interlocked类的一个模式进行补充说明。如果没用过Interlocked类,可以看看上面的这篇文章。
这个模式的名字是Jeffrey给起的,它究竟要解决什么问题,我们为什么要用它?带着这些疑问,我们来看看它的应用场景。
看看下面这个设定最大值的例子:
private static Int32 Maximum(ref int target, int value) { int oldValue; oldValue = target; //计算最大值 int temp = Math.Max(target, value); //注意:这里可能有其他线程恰好修改了target的值 //如果target是oldValue,则替换成最大的值 Interlocked.CompareExchange(ref target, temp, oldValue); return target; }
测试代码:
int v1 = 10; v1 = Maximum(ref v1, 20); Console.WriteLine("v1:" + v1);
如果恰好有一个线程在Interlocked.CompareExchange这句代码执行前修改了target的值为15,那么我们这里的最大值20就不会生效,因为CompareExchange的比较条件将会失败。我们得到的输出,有可能是:v1:15。
这样的结果可能不是我们的预期,问题在于上面的代码只调用了CompareExchange一次,如果不成功就跳过了这次修改。所以,我们可以考虑对CompareExchange做一个循环判断,不成功就再度尝试修改,直到成功为止。而这正是Jeffery所说的Interlock Anything模式。
我们进一步修改上面的代码:
private static Int32 Maximum(ref int target, int value) { int oldValue, currentValue; currentValue = target; do { //oldValue记录当前循环开始时的原始值 oldValue = currentValue; //计算最大值 int temp = Math.Max(target, value); //注意:这里可能有其他线程恰好修改了target的值 //如果target是oldValue,则替换成最大的值。 //如果被其他线程修改了,currentValue返回的将会是被其他线程修改后的最新值,比如:15 currentValue = Interlocked.CompareExchange(ref target, temp, oldValue); } while (currentValue != oldValue);//如果被修该,则进入下一次循环,尝试再次修改 return target; }
注释中已经写得很清楚了,可能你需要反复的思考一下上面的代码,理解其意图。这里只是对Int32的值进行了修改,利用这个模式实际可以对任何引用类型的值进行修改。从而避免多线程访问共同变量带来的冲突。
对于这个模式的应用,我们看看微软给我们的示范:在实现事件(Event)时是如何运用了这个模式的。
首先定义下面的一个事件:
public delegate bool UploadFileCompleteEventHander(string fileName); public event UploadFileCompleteEventHander UploadFileComplete;
我们知道,事件本身还是用代理来实现的,只不过编译器帮我们做了这部分工作,反编译这个事件的代码:
public event UploadFileCompleteEventHander UploadFileComplete { add { UploadFileCompleteEventHander hander2; UploadFileCompleteEventHander uploadFileComplete = this.UploadFileComplete; do { hander2 = uploadFileComplete; UploadFileCompleteEventHander hander3 = (UploadFileCompleteEventHander) Delegate.Combine(hander2, value); uploadFileComplete = Interlocked.CompareExchange<UploadFileCompleteEventHander>(ref this.UploadFileComplete, hander3, hander2); } while (uploadFileComplete != hander2); } remove { UploadFileCompleteEventHander hander2; UploadFileCompleteEventHander uploadFileComplete = this.UploadFileComplete; do { hander2 = uploadFileComplete; UploadFileCompleteEventHander hander3 = (UploadFileCompleteEventHander) Delegate.Remove(hander2, value); uploadFileComplete = Interlocked.CompareExchange<UploadFileCompleteEventHander>(ref this.UploadFileComplete, hander3, hander2); } while (uploadFileComplete != hander2); } }
看见了吗,上面的add和Remove方法,对代理的添加和删除就是用的这个模式,多么经典。保证多个线程注册一个事件时,不会出现冲突。
总结:
1,一个变量可能被多线程访问时,可以考虑运用这个模式。
2,它的速度比阻塞式同步,如lock等,要快很多。
3,它能对任何类型的变量进行赋值,而不仅仅局限于Int32,等原子操作类型。