【多线程笔记】原子操作-Interlocked

乐观锁与悲观锁:

众所周知锁有两种:乐观锁与悲观锁。独占锁是一种悲观锁,而 Lock 就是一种独占锁,Lock 会导致其它所有未持有锁的线程阻塞,而等待持有锁的线程释放锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止,这个过程叫自旋。而乐观锁用到的机制就是CAS。

使用乐观锁还是悲观锁

两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的吞吐量。但如果是多写的情况,一般会经常发生冲突,这就会导致CAS算法会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

CAS

CAS是一种有名的无锁算法。无锁编程,即不适用锁的情况下实现多线程之间的变量同步,也就是在没有现成被阻塞的情况下实现变量的同步。
CAS在.NET中的实现类是Interlocked,内部提供很多原子操作的方法,最终都是调用Interlocked.CompareExchange()
说到线程安全,不要一下子就想到加锁,尤其是可能会调用频繁或者是要求高性能的场合。

为什么说CAS是乐观锁

乐观锁,严格来说并不是锁,通过原子性来保证数据的同步,比如说数据库的乐观锁,通过版本控制来实现,所以CAS不会保证线程同步。乐观的认为在数据更新期间没有其他线程影响。

CAS原理

CAS在硬件层面提升效率,多个语句原子操作不可分割。在intel的CPU中,使用cmpxchg指令。
CAS,是“Compare And Swap”的缩写,中文简称就是“比较并替换”,在CAS算法中,它使用了3个基本操作数:内存地址对应的值V,旧的预期值A(旧值),要修改的新值B(新值),当且仅当预期值A和内存值V相同时,才将内存值修改为B,否则什么都不做,最后返回现在的V值。

总结

  • CAS(Compare And Swap)比较并替换,是线程并发运行时用到的一种技术
  • CAS是原子操作,保证并发安全,而不能保证并发同步
  • CAS是CPU的一个指令(需要JNI调用Native方法,才能调用CPU的指令)
  • CAS是非阻塞的、轻量级的乐观锁

CAS的适用场景

读多写少:如果有大量的写操作,CPU开销可能会过大,因为冲突失败后会不断重试(自旋),这个过程中会消耗CPU
单个变量原子操作:CAS机制所保证的只是一个变量的原子操作,而不能保证整个代码块的原子性,比如需要保证三个变量共同进行原子性的更新,就不得不使用悲观锁了

Interlocked

MSDN 描述:为多个线程共享的变量提供原子操作。主要函数如下:
Interlocked.Increment    原子操作,递增指定变量的值并存储结果。
Interlocked.Decrement   原子操作,递减指定变量的值并存储结果。
Interlocked.Add        原子操作,添加两个整数并用两者的和替换第一个整数
Interlocked.Exchange原子操作,赋值
Interlocked.CompareExchange(ref a, b, c); 原子操作,a参数和c参数比较, 相等b替换a,不相等不替换。方法返回值始终是第一个参数的原值,也就是内存里的值

Interlocked.Exchange方法

这个代码很简单,就做了2个事情,1是使用Interlocked.Exchange将_flag变量进行赋值。2是将Interlocked.Exchange操作后返回的原始值与_flag变量进行对比,如果相等说明这个变量已经被修改过了,表示这里是重入了。如果不是则说明第一次进入此方法。

        private static int _flag;

        public void ExactlyOnceMethod()
        {
            var original = Interlocked.Exchange(ref _flag, 1);
            if (original == _flag)
            {
                // 1.重复进入
            }
            else
            {
                // 2.第一次进入
            }
        }

Interlocked.CompareExchange

使用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++)
                {
                    Increment();
                }
            };
            var t1 = Task.Run(action);
            var t2 = Task.Run(action);
            Task.WaitAll(t1, t2);
            Console.WriteLine(count);//线程安全,count结果等于100000
            Console.ReadKey();
        }

参考:
https://www.jianshu.com/p/db5c964a61ee(CAS)

posted @ 2020-07-19 16:22  .Neterr  阅读(617)  评论(0编辑  收藏  举报