Interlocker

Interlocker

前言

刚才看了一下一个关于白帽黑客的视频,里面说了一句话我甚是认同啊,没有牛逼的人物,只有牛逼的技术.好好学技术,争取成为牛逼的人物.

扫盲

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

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

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

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace InterLocked的使用
{
    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);
                });

                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 *= y;
            }
        }
    }
}
 

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

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

由于t2是原子操作,所以保证x的值可以被自增,不会受到其他现成的干扰.t1中运行的方法是加了锁的方法.放放风运行的时候,要么t1先运行,要么t2要运行,so,要么值为(2+1)*10=30,要么值为(2*10)+1=21.

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

当t2在读取完x的值后,把该值读到寄存器中,寄存器中的值为2了,而t1中的原子操作对x做了自增,也就是受此时x是3了,接着,t2线程在寄存器中把该值乘以19以后,写入到内存了,然后下一个时刻释放锁,此时就把之前的值又覆盖成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方法,办法就是把t2中也加锁:

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

这种方法其实也不好,因为这会使得线程不能真正的运行,总有一个线程陷于等待中.

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-18 16:02:29

Interlocker的相关文章

C#各种同步方法 lock, Monitor,Mutex, Semaphore, Interlocked, ReaderWriterLock,AutoResetEvent, ManualResetEvent

看下组织结构: System.Object System.MarshalByRefObject System.Threading.WaitHandle System.Threading.Mutex System.Threading.Semaphore System.Threading.EventWaitHandle System.Threading.ManualResetEvent System.Threading.AutoResetEvent System.Object System.Thre

在Wince上实现Snmp

距离第一次写snmp的博客已经4年了,刚好最近迷上了wince就把原来的库移植到了wince上,经过一番整理,发现wince上不会比pc上更难编写代码,许多代码稍微改了下就很方便的移植过去了,里面也有一些小问题,比如Interlocker.Add这个函数wince上就不支持.HashSet<T>wince上也不支持.都是一些细节问题,真正花在移植上的时间并不多,加起来5个小时就够了,这几天又稍微优化了一下,写了个简单agent,速度也不错.支持zlg的iMAX283的开发板,支持wince模拟