• Volatile vs. Interlocked vs. lock


    今天在stackoverflow上看到一个关于Volatile, Interlock, Lock的问题,发现回答的特别好,所以就想到把它翻译一下, 希望给那些对它们有疑惑的人提供点帮助

    image:假设有一个类,它含有一个可以被多线程访问的public int counter 字段, 这个数字只会增加或减少。

    当去增加这个字段的时候,应该采用下面哪个方案,为什么?

    • lock(this.locker) this.counter++;
    • Interlocked.Increment(ref this.counter);
    • Change the access modifier of counter to public volatile

    image

    最糟糕(实际上都不能起作用)

    Change the access modifier of counter to public volatile

    这种方式实际上根本不安全,关于volatile的重点是:运行在多个CPU上的多个线程会缓冲数据和重新排列执行的指令。

    如果它是非volatile, 当CPU A增加了一个值, CPU B需要等一会才能看到增加的值,这可能导致问题的出现。

    如果它是volatile,就能确保两个CPU能在相同的时间看到相同的值。但它并不能避免交叉的读写操作。

    给一个变量增加值,实际上需要三步

    1. 读, 2. 增加 3.写

    假设线程A 读取 counter 的值为1,还没有准备增加,这时线程B也读取counter 的值为1, 然后两个线程都开始执行增加,写的操作。最终counter 的值为2. 这是不对的,两个线程都做了增加操作,正确的结果应该是3. 所以把它标志为volatile根本就是不安全的。

    比较好

    lock(this.locker) this.counter++;

    这种方式是安全的(当然你要记得lock所有你想访问this.counter的地方)。它防止其它任何线程去执行被lock锁住的代码。并且它还能防止上面提到的多CPU指令排序的问题。但问题是,lock在性能上比较慢,并且如果你在其它的一些不相干的地方也用了lock,可能会导致阻塞你的其它线程。

    最好

    Interlocked.Increment(ref this.counter);

    这个是安全的,也是非常高效的。它执行读,增加,写三个操作在一个原子中,不会在中间被打断。因为它不影响其它的代码,你也不需要记住其它地方的Lock. 并且它还非常快(正如MSDN说的,在现在的CPU上,它常常只是一个指令)。

    但是我不完全确定它能否也能解决CPU的指令排序的问题,还是需要结合volatile和这个increment一起使用。

    补充:到底volatile是擅长解决什么问题的?

    既然volatile不能防止多线程的问题,那它能干啥用?举个很好的例子,假设你有两个线程,一个总是往一个变量中进行写操作,假设这个变量为queneLength, 另一个总是从这个变量中读取数据。如果queueLenght不是volatile的, 线程A可能读了5次,但是线程B可能看到的是延迟的数据,甚至可能看到错误的顺序的数据。一个方案就是用lock, 但是在这种情况系你也可以用volatile. 这样能确保线程B总能看到线程A写的最新数据,但是这个这个逻辑仅仅在你写的时候没有读,读的时候没有写时才有效。一旦多个线程都要做读-修改-写的操作,你需要用Interlocked或者用lock.

  • 相关阅读:
    spring boot +mybatis 操作sqlite数据库
    katalon studio教程之通过录制/回放创建测试用例
    #katalon studio# 安装和设置(Installation and Setup)
    NET Core 基于Aspect Injector 实现面向AOP编程
    NET Core 3.1使用AutoMapper实现对象映射
    给NET core 智能感知提示安装中文汉化包
    代码注释规范
    软件升级版本号迭代规范-Semantic Versioning
    使用阿里云的Helm私有仓库
    Helm操作指南
  • 原文地址:https://www.cnblogs.com/peteryan/p/3794853.html
Copyright © 2020-2023  润新知