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)
- C++11的条件变量跟win32的Event有一个区别,就是必须在wait之后signal,否则就必须结合预测条件(从而检查是否在wait之前已经signal)。C++ Core Guidelines: Be Aware of the Traps of Condition Variables
Atomic
- Does the C++ 11 standard guarantees that std::atomic<> is implemented as a lock-free operation?,C++11不保证std::atomic
是lock-free的,而是由数据类型长度等因素决定的,可以用std::atomic ::is_lock_free()来判断该类型是否lock-free。 - What is the difference between explicit atomic load/store and usual operator= and operator T?,两者是相等的,后者使用默认的memory_order_seq_cst级别内存屏障。
本文地址:http://xnerv.wang/thread-synchronization-and-atomic-operation/