本随笔续接:.NET同步与异步之相关背景知识(六)
在上一篇随笔中已经提到、解决竞争条件的典型方式就是加锁 ,那本篇随笔就重点来说一说.NET提供的最常用的锁 lock关键字 和 Monitor。
一、lock关键字Demo
public object thisLock = new object(); private long index; public void AddIndex() { lock (this.thisLock) { this.index++; if (this.index > long.MaxValue / 2) { this.index = 0; }
// 和 index 无关的大量操作 } } public long GetIndex() { return this.index; }
这一组demo,代码简洁,逻辑简单,一个 AddIndex 方法 保证字段 index 在 0到100之间,另外一个GetIndex方法用来获取字段index的值。
但是,这一组Demo却有不少问题,甚至可以说是错误,下面我将一一进行说明:
1、忘记同步——即读写操作都需要加锁
GetIndex方法, 由于该方法没有加锁,所以通过该方法在任何时刻都可以访问字段index的值,也就是说会恰好在某个时间点获取到 101 这个值,这一点是和初衷相违背的。
2、读写撕裂
如果说读写撕裂这个问题,这个demo可能不是很直观,但是Long类型确实存在读写撕裂。比如下面的例子:
/// <summary> /// 测试原子性 /// </summary> public void TestAtomicity() { long test = 0; long breakFlag = 0; int index = 0; Task.Run(() => { base.PrintInfo("开始循环 写数据"); while (true) { test = (index % 2 == 0) ? 0x0 : 0x1234567890abcdef; index++; if (Interlocked.Read(ref breakFlag) > 0) { break; } } base.PrintInfo("退出循环 写数据"); }); Task.Run(() => { base.PrintInfo("开始循环 读数据"); while (true) { long temp = test; if (temp != 0 && temp != 0x1234567890abcdef) { Interlocked.Increment(ref breakFlag); base.PrintInfo($"读写撕裂: { Convert.ToString(temp, 16)}"); break; } } base.PrintInfo("退出循环 读数据"); }); }