God's in his heaven.
All's right with the world.

0%

线程同步与原子操作

volatile

  • volatile使得代码每次在读写volatile变量时都需要从内存读写,而不能使用寄存器中缓存的值。并且也禁止编译器对volatible做编译优化。volatile本身并不是用于线程同步,也不保证原子读写(例如volatile a++这种需要几个指令才能完成的操作)。volatile主要用于access to memory mapped devices和variables in signal handlers and between setjmp and longjmp。C++标准禁止编译器reorder同一个线程内的volatile变量的读写,但不同线程则没有限制。non-volatile变量则有可能发生reorder(Stay away from Volatile in threaded code?)。
  • 而根据为什么volatile++不是原子性的?中的说法,volatile的读操作后会插入LoadLoad和LoadStore屏障,避免volatile读操作与后面的普通读写发生reorder。而volatile的写操作前会插入StoreLoad和StoreStore屏障,避免volatile写操作与后面的普通读写发生reorder(我不太确定这种说法的正确性,毕竟在wikipediavolatile (computer programming)中并没有提到volatile会插入内存屏障,或者只有Java等语言才会这样做?)。
  • 内存屏障中有提到

    C与C++语言中,volatile关键字意图允许内存映射的I/O操作。这要求编译器对此的数据读写按照程序中的先后顺序执行,不能对volatile内存的读写重排序。因此关键字volatile并不保证是一个内存屏障。[4]
    对于Visual Studio 2003,编译器保证对volatile的操作是有序的,但是不能保证处理器的乱序执行。因此,可以使用InterlockedCompareExchange或InterlockedExchange函数。
    对于Visual Studio 2005及以后版本,编译器对volatile变量的读操作使用acquire semantics,对写操作使用release semantics。

  • volatile跟const一样属于变量修饰符,因此也和const一样必须弄清楚修饰的是指针还是变量自身(或者甚至是第几级指针)。例如uchar * volatile reg;说明指针reg本身是volatile的,而volatile uchar *reg;说明*reg(也就是reg指向的变量)是volatile的。而且volatile也可以和const同时使用。
  • volatile陷阱一文中有提到几种volatile的陷阱和误用。
  • “Volatile” can be harmful…中提到可以将函数参数标记为volatile避免编译器优化,从而便于debug。

临界区块(Critical section)

  • Windows的CRITICAL_SECTION首先会在用户态自旋尝试几次获取锁,如果最终还是失败的话就会内核模式等待。

内存屏障(Memory barrier)

  • 首先要明白的一点,reorder不仅可以发生在编译时,也可以发生在运行时。CPU流水线也可以重排某些指令顺序。所以即使是同一段编译好的程序,不同的CPU内核也可能执行不同的指令顺序。(Volatile and memory barriers
  • 根据Memory barrier,如果只有单个CPU,即使发生reorder也不会有什么问题,问题都是发生在多个线程之间。而且内存屏障只在运行时生效???而编译时需要用volatile?并且这篇wiki也提到volatile并不能阻止volatile变量和non-volatile变量的reorder。本质上,C/C++标准中volatile是通过控制编译器而实现的(虽然编译器实现有可能引入内存屏障来实现volatile),而内存屏障是通过特殊CPU指令实现的)。
  • How do I Understand Read Memory Barriers and Volatile中将对内存的操作比喻成有一个queue(因为CPU比内存快),所以Acquire操作就是flush all read requests in the queue(实际上并不是flush,而只是加一个标记,所以叫做内存屏障),而Release操作就是flush all write requests in the queue。因此在Acquire(lfence)之前的read requests一定会完成在Acquire之前,而Release(sfence)之前的write requests一定会完成在Release之后。类似于“半透膜”的效果。(那也就是说Acquire是LoadLoad屏障,而Release是WriteWrite屏障?)而full barrier (or full fence, mfence)就是禁止在此之前的所有read/write操作被reorder到本操作之后。
  • 而C++11中的Acquire和Release定义则与上面的回答不同。C++11中的Acquire是LoadLoad+StoreLoad,Release是LoadStore+StoreStore。结合C++11内存模型内存屏障与内存模型C++内存屏障(内存顺序)总结std::memory_order等文章,6中内存屏障级别的区别是:

    (建议仔细再阅读最后一篇文章。不太明白的是,当论及other thread acquire/release the same atomic时,是指代码上有acquire/release操作的线程,还是指在某一个时刻瞬间进行了acquire/release的线程?就目前看来前者的可能性更大一些)

条件变量(Condition Variable)

Atomic


本文地址:http://xnerv.wang/thread-synchronization-and-atomic-operation/