C#中Interlocked不能保证的事情-创新互联-成都快上网建站

C#中Interlocked不能保证的事情-创新互联

本文的参考,来自于文章http://blogs.msdn.com/b/oldnewthing/archive/2004/09/15/229915.aspx

成都创新互联成都企业网站建设服务,提供网站建设、做网站网站开发,网站定制,建网站,网站搭建,网站设计,响应式网站,网页设计师打造企业风格网站,提供周到的售前咨询和贴心的售后服务。欢迎咨询做网站需要多少钱:18982081108

Interlocked类MSDN中对他的定义为:为变量在多线程共享的情况下提供原子操作。

很多人对于Interlocked的使用,仅限于Interlocked.Increment方法,这个方法在多线程环境下,总可以保证变量自增的正确性。

那么原子方法的定义是什么呢?顾名思义,原子一般认为是不可再分的,所以原子方法就是不可再分的方法,即在一个原子操作中,处理器能够在一个指令传输中完成读值和写值, 也就是说,在原子操作完成之前,任何IO机制或处理器都不能对这个内存进行读写操作。原子操作常常被使用于大多数操作系统的内核和基础类库中,而且大多数的计算机硬件,编译器和类库都支持了各个层面上的原子操作。

很多人认为使用原子操作能够保证多线程下的数据的正确性。那么看下面的代码:

class Program
{
static private object _obj = new object();
static void Main(string[] args)
{
int count = 0;
for (int i = 0; i < 10000; i++)
{
int x = 2;
Task t1 = Task.Factory.StartNew(() => { InterlockedMultiply(ref x, 10); });
Task t2 = Task.Factory.StartNew(() => { Interlocked.Increment(ref x); });
Task.WaitAll(t1, t2);
if (x != 21 && x != 30)
{
Console.WriteLine(x); 
count++;
}
}
Console.WriteLine("值不为21且不为30的次数:"+count);
Console.WriteLine("done!");
}
static void InterlockedMultiply(ref int x, int y)
{
lock (_obj)
{
x = x * y;
}
}
}

上述代码中,循环体中,开2个task,可以认为是2个线程,这2个线程运行2个方法,一个是原子方法,另一个是加了锁的方法。

先用常规的思路分析一下:

由于t2是原子操作,所以保证x的值可以被自增,不会受其他线程的干扰,t1中运行的方法是InterlockedMultiply,并加入lock。

当方法运行的时候,要么是t1先运行,要么是t2先运行,所以,要么值为(2+1)*10=30,要么值为(2*10)+1=21。

但是当运行的时候,循环10000次后,结果为:

C#中Interlocked不能保证的事情

值不为21且不为30的次数:103。

导致这种问题的原因,在于InterlockedMultiply的实现,看下面的细节图

C#中Interlocked不能保证的事情

当t2在读取完x的后,把该值读到寄存器中,寄存器中的值为2了,而t1中的原子操作对x做了自增,也就是说此时x是3了,接着,t2线程在寄存器中把该值乘以10后,写入到内存,然后下一个时刻释放锁,此时就把之前的值有覆盖成20了。

也就是说虽然2个线程中的代码都是各自独立的,但是在并发情况下,还是不能保证值的正确性。原因如下:

1.由于我们模拟的InterlockedMultiply方法中的操作,并不具备原子性。虽然加了lock,但是对于那些无需获取锁就能访问x的函数体来说,它们是可以直接修改x的。也就是说,这里加了lock也没什么用。

2.Interlocked.Increment能够保证的是该操作是原子性的,在它将值设为3的期间,不会有其他操作读写x。(这点很重要,如果这点不能保证,那么上面的情况中会出现:

假设原本step6,step7中的操作在step5期间进行,当t2的操作快于t1先完成时,那么X的值先被设成了20,然后又立刻被t1设成了3,这就导致了值会是3的情况出现。)

3.虽然Interlocked.Increment使得x的自增具备原子性,但它不能够保证的是:但它不能够保证的是:其他线程在操作x时,拿到的x的值是最新的。

基于上面的分析,可以得出一个结论,多线程下的并发编程,需要考虑的更周到,原子操作并不保证多线程环境下的值总是对的。

如果要实现上面的InterlockedMultiply方法,参考文档中的写法是采用了do while的循环,这个操作就使得t2线程在那边不断的重试,其实也不是最好的办法。我能想到的办法是把t2中也加锁:

Task t2 = Task.Factory.StartNew(() => { lock (_obj) { Interlocked.Increment(ref x); } });

但是这种办法也不好,使得线程不能正真的同时运行,总有一个陷于等待中。

另外有需要云服务器可以了解下创新互联scvps.cn,海内外云服务器15元起步,三天无理由+7*72小时售后在线,公司持有idc许可证,提供“云服务器、裸金属服务器、高防服务器、香港服务器、美国服务器、虚拟主机、免备案服务器”等云主机租用服务以及企业上云的综合解决方案,具有“安全稳定、简单易用、服务可用性高、性价比高”等特点与优势,专为企业上云打造定制,能够满足用户丰富、多元化的应用场景需求。


本文名称:C#中Interlocked不能保证的事情-创新互联
文章网址:http://kswjz.com/article/djoojh.html
扫二维码与项目经理沟通

我们在微信上24小时期待你的声音

解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流