乐观锁与悲观锁:
众所周知锁有两种:乐观锁与悲观锁。独占锁是一种悲观锁,而 Lock 就是一种独占锁,Lock 会导致其它所有未持有锁的线程阻塞,而等待持有锁的线程释放锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止,这个过程叫自旋。而乐观锁用到的机制就是CAS。
CAS(Compare And Set):比较和交换
CAS,是“CompareAnd Swap”的缩写,中文简称就是“比较并替换”,在CAS算法中,它使用了3个基本操作数:内存地址对应的值V,旧的预期值A(旧值),要修改的新值B(新值),当且仅当预期值A和内存值V相同时,才将内存值修改为B,否则什么都不做,最后返回现在的V值。
CAS在.NET中的实现类是Interlocked,内部提供很多原子操作的方法,最终都是调用Interlocked.CompareExchange()
说到线程安全,不要一下子就想到加锁,尤其是可能会调用频繁或者是要求高性能的场合。
CAS的适用场景:
读多写少:如果有大量的写操作,CPU开销可能会过大,因为冲突失败后会不断重试(自旋),这个过程中会消耗CPU
单个变量原子操作:CAS机制所保证的只是一个变量的原子操作,而不能保证整个代码块的原子性,比如需要保证三个变量共同进行原子性的更新,就不得不使用悲观锁了
Interlocked:
MSDN 描述:为多个线程共享的变量提供原子操作。主要函数如下:
Interlocked.Increment 原子操作,递增指定变量的值并存储结果。
Interlocked.Decrement 原子操作,递减指定变量的值并存储结果。
Interlocked.Add 原子操作,添加两个整数并用两者的和替换第一个整数
Interlocked.Exchange原子操作,赋值
Interlocked.CompareExchange(ref a, b, c); 原子操作,a参数和c参数比较, 相等b替换a,不相等不替换。方法返回值始终是第一个参数的原值,也就是内存里的值
使用CompareExchange方法实现Increment功能:
public static void Increment() { int init = 0, result = 0; do { init = count; result = init + 1; } //当init == count时, count=result;当init !=count时, count不变化; while (init != Interlocked.CompareExchange(ref count, result, init)); }
测试:
static int count = 0; static void Main(string[] args) { Action action = () => { for (int i = 0; i < 50000; i++) { //count++;//线程不安全,结果小于100000 //Interlocked.Increment(ref count);//线程安全,结果等于100000 Increment();//线程安全,结果等于100000 } }; var t1 = Task.Run(action); var t2 = Task.Run(action); Task.WaitAll(t1, t2); Console.WriteLine(count); Console.ReadKey(); }
参考: