.NET线程同步之Interlocked构造

来源:互联网 发布:js中校验身份证合法性 编辑:程序博客网 时间:2024/06/06 01:12

接着上一篇博客.NET线程同步之Volatile构造,本篇来讨论如何使用Interlocked来处理线程安全的原子性读写操作。

引出问题

开始详细介绍System.Threading.Interlocked类之前,我们来先实现一个功能:在多个线程中同时增长一个公共变量。

static void Main(string[] args){    Parallel.For(0, 10000, i => ChangeValue(1));    Console.WriteLine(value);}static int value = 0;static void ChangeValue(int addValue){    value += addValue;}

上面的代码创建了10000个任务,每个任务都让公有变量value加一,线程池会分配线程来运行这10000个任务,由于避免不了并发,所以,总会有一些写入操作同时执行而造成value的更新被覆盖,所以,上面代码的运行结果经常是小于10000的。具体原因如下:
在某个时间点,假设当前公共变量value的值是1000,有两个线程同时将value加一并写入,value最终的结果就会变成1001,而不是我们希望的1002。

使用Interlocked类来解决问题

下面使用Interlocked.Add()方法来实现。

static void Main(string[] args){    Parallel.For(0, 10000, i => ChangeValueWithInterlock(1));    Console.WriteLine(value);}static int value = 0;static void ChangeValueWithInterlock(int addValue){    Interlocked.Add(ref value, addValue);}

运行上面的代码,每次返回的结果都是10000。
Interlocked类的每一个方法都执行一个原子级的读取并写入操作。并且,它的每一个方法都是建立了完整的内存栅栏(memory fence),可以保证在Interlocked的方法在执行期间,不允许其它方法对指定的共有变量进行写入和读取。

Interlocked类的常用方法

下面列举几个Interlocked类的常用方法。这个类不仅支持int类型,还支持float、double、long类型以及对象类型(有一个泛型重载)。在此我仅列举int类型的版本。

public static int Add(ref int location, int value)

上面这个是刚才我们使用的方法,对location所在的变量进行原子级的add。

public static int Increment(ref int location)

上面这个方法相当于Interlocked.Add(ref location, 1)

public static int Decrement(ref int location)

上面这个方法相当于Interlocked.Add(ref location, -1)

public static int Exchange(ref int location, int value)

上面这个方法稍微复杂一点,但是很常用,它将location所对应的变量的值替换为value,并返回location被替换之前的值。假设多个线程同时竞争一个公共资源,可以定义一个值为0的公共变量,然后调用Interlocked.Exchange()方法去将它更新为1,哪个线程的方法的返回值是0,则这个线程在竞争中胜出。因为只有一个线程能够将这个共有变量由0更新为1。这也是SpinLock的大致实现原理。我在接下来的博客中会专门介绍SpinLock的用法。

public static int CompareExchange(ref int location, int value, int comparand)

上面这个方法更复杂一点,它先比较locating所对应的变量的值是否等于comparand,如果相等,则将location对应的变量更新为value。这个方法总是返回location原来的值,所以,如果comparand的值等于该方法的返回值,则说明location对应的变量被成功地更新了。

这个方法的用法可以非常灵活,我介绍其中的一种吧,它可以用来做乐观并发,具体来说是这样子的:假设你要更新一个公共资源,你先读取它当前的值保存在一个临时变量中,然后可以去做点别的操作,最后调用Interlocked.CompareExchange()方法去更新它的值,如果该方法返回的值跟临时变量中的值不一样,说明你读到了脏数据,更新失败!需要再次重复这个流程来更新。

阅读全文
0 0