《剖析为什么在多核多线程程序中要慎用volatile关键字?》有45个想法

  1. LZ更新的很快,支持!
    慢慢看,我最近也研究了volatile相关知识,我们肯定理解的有所不同,到时讨教一下

    现在有个问题确实有些令我费解(我没有lz的QQ啥的,所以只要这里一问).
    问题起因:
    UNP和其他一些书籍(具体哪一页不记得了)都讲到pthread_cond_wait()函数中解锁和睡眠部分
    是一个原子操作。是的,我认为也应该是原子操作才行。
    但问题是:
    glibc关于pthread_cond_wait源码:
    /* Prepare to wait. Release the condvar futex. */
    lll_unlock (cond->__data.__lock, pshared);

    /* Enable asynchronous cancellation. Required by the standard. */
    cbuffer.oldtype = __pthread_enable_asynccancel ();

    /* Wait until woken by signal or broadcast. */
    lll_futex_wait (&cond->__data.__futex, futex_val, pshared);
    可以看到解锁和睡眠部分完全有可能不是原子操作的,内核完全可以在两者之间调度。这就有矛盾了。
    lz知道怎么回事吗?谢谢啊
    /* Disable asynchronous cancellation. */
    __pthread_disable_asynccancel (cbuffer.oldtype);

    1. 这个很好理解,pthread_cond_wait()每次被调用时都要有一个辅助的mutex需要加锁/解锁,在lock与unlock之间被保护的整个临界区内的操作就是“原子的”。pthread_cond_wait()内部实现时会先解锁,再休眠,再被唤醒,再加锁,这个过程等于有两个临界区,这两个临界区之间不是原子的,但是临界区内部是原子的。

      1. 是的,lll_unlock(),lll_lock()就可以看出来。
        但这里的问题是pthread_cond_wait()先释放了辅助的mutex(通过lll_unlock()),
        然后再休眠(通过lll_futex_wait()),中间还有下面代码:
        /* Enable asynchronous cancellation. Required by the standard. */
        cbuffer.oldtype = __pthread_enable_asynccancel ();
        所以内核完全有可能在两端代码间调度。当pthread_cond_wait()释放了辅助的mutex(通过lll_unlock())时,pthread_cond_signal()完全可以得到这个辅助的mutex(通过lll_lock()),这样的话,可以看出解锁和休眠不是一个原子操作(注意:这里的说的原子就是 你说的 lock与unlock之间被保护的整个临界区内的操作就是“原子的”,我说的也是这种原子,而不是像i++操作的原子)。

        1. 你是说在pthread_cond_wait()的内部实现中,在lll_unlock()被执行之后且在lll_futex_wait()被执行之前另一个线程已经发送完信号,从而导致信号被pthread_cond_wait()错过的情况?

          1. 是的,就是这意思。
            但又跟unp等书说的解锁和休眠是一个原子操作相矛盾
            所以有了这个疑问
            你了解吗?

          2. 好像确实是有问题 但是我感觉pthreads在实现时肯定已经考虑了这种情况 因为pthreads出现了这么长时间 肯定已经解决过类似问题了
            你可以看看这篇论文讲到了条件变量实现时一些可能的问题,还有这篇
            等我有时间我再好好研究下:)

  2. 恩,大体看了一下这篇文章,大体同意。
    如果能保证对齐和原子操作,那么像volatile i;i++这样的操作完全是可以不用锁的

    1. i++这样的操作如何保证原子性?它是三条汇编语句组成的,volatile根本不能保证这个操作的原子性;而像i=1这样的写操作的原子性是由x86硬件保证的。想保证i++这样的操作的原子性要么使用并行库中atomic variable里面的increase方法,它们是保证原子性的,要么就是用带LOCK语义的汇编语句。

      1. 我的意思是在可能的某种机器中,i++本身就是原子的,不是有三条指令组成,只是有一条指令组成

          1. “__asm LOCK inc dword ptr[x]” 这个加了总线锁了
            如果能保证是一个原子操作,那么i++就是原子的

  3. 很不错!
    有个问题请教一下博主,在你的另外一篇博文”多线程队列的算法优化”中提到,”好在一般来讲next指针是32位数据,而现代的CPU已经能保证多线程程序中内存对齐了的32位数据读写操作的原子性,而一般来讲编译器会自动帮你对齐32位数据,所以这个不是问题。”, 这里next不是volatile类型, 如果在一个线程中next放在寄存器中,对它的修改没有写回内存中;同时另一个线程从内存中读这个next指针,如何保证它们的一致性?

    1. 多线程对同一个值的读写的正确性是由Cache Coherency保证的 在你说的情况下 被保存在不同core的cache中的next值会由CC保证一致性 可以参考我这篇文章:http://www.parallellabs.com/2010/03/06/why-should-programmer-care-about-sequential-consistency-rather-than-cache-coherence/

    1. 这个问题很有意思:直接与寄存器打交道的汇编代码是编译器产生的,跟Cache一致性没什么关系。本质上这个问题的关键就是,如果在多线程间用来同步的变量被缓存在寄存器里了,其他线程就有可能看不见它的最新值从而程序出错。要解决这个问题可以通过给这个变量添加volatile关键字来强迫编译器不要做此种缓存在寄存器中的优化,即每次访问都在主存中读/写最新值。
      找到了一篇相关解答(Eddycharly发的那个帖子): http://www.gamedev.net/community/forums/topic.asp?topic_id=590911
      我还没测试这个代码,等回头抽空测试一下看看有没有什么有趣的结果。多谢你的好问题!

  4. volatile不能用在并发程序中。事实上用在计数器场景比较多,就是一个线程专门对这个计数器操作,另外一个或多个线程来获取这个值,获取的时候允许一定的滞后性。这时候用volatile就行了,免得那个更新计数器的线程只会在寄存器里更新, 或者一直放在cache里。导致不可容忍的滞后性

  5. 很全面。关于java的volatile的说明,有点问题:
    ———————-
    Java和.NET中的volatile关键字也是限制虚拟机做优化,都具有acquire和release语义,而且由虚拟机直接保证了对volatile变量读写操作的原子性。
    ———————-
    volatile只保证可见性,不保证原子性。java中,对volatile修饰的long和double的读写就不是原子的(http://java.sun.com/docs/books/jvms/second_edition/html/Threads.doc.html#22244),除此之外的基本类型和引用类型都是原子的。

  6. 它唯一允许的乱序优化是可以把对不同地址的load操作提到store之前去(即把store x->load y乱序优化成load y -> store x)。而store x -> store y、load x -> load y,以及store x -> load y不允许交换执行顺序。

    最后的”以及store x -> load y”应该是load y -> store x吧?

  7. “而store x -> store y、load x -> load y,以及store x -> load y不允许交换执行顺序。”这句中的store x -> load y应该是load y ->store x 吧?

  8. 为什么把多线程的问题全部归结到volatile上?这也躺枪。。。
    乱序执行还是原子操作真心不关volatile的事

    1. 你好,“全部归结到volatile上”不是我原文想表达的意思。我只是想提醒大家volatile的一些陷阱而已:)

  9. 没看明白为什么“使用volatile”是危险的。volatile的设计初衷就不是用来保障线程安全的,如果一个程序员以为加了volatile,就线程安全了,那是他的错误。这怎么也怪到volatile头上去了……
    一个本来线程安全的程序,给其中的一些变量加上volatile,会导致“危险”么?就算不恰当的多余的volatile,也只是导致一些效率损失。volatile根本不是问题所在。

发表评论

电子邮件地址不会被公开。 必填项已用*标注