pthreads如何阻止编译器跨pthread_mutex_lock调用将变量缓存到寄存器
问题背景
假设存在两个线程访问受锁保护的全局变量var,示例代码如下:
#include <pthread.h> #include <stdio.h> pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; static int var; void *thread1_func(void *ptr) { pthread_mutex_lock(&lock); var = var + 1; printf("Thread 1: %d\n", var); pthread_mutex_unlock(&lock); } int main() { pthread_t thread1; var = 0; pthread_create(&thread1, NULL, thread1_func, NULL); pthread_mutex_lock(&lock); printf("Thread 0: %d\n", var); pthread_mutex_unlock(&lock); pthread_join(thread1, NULL); return 0; }
假设指令按如下顺序交错执行:
Thread 0: Thread 1: 1. var = 0; 2. pthread_create(&thread1, NULL, thread1_func, NULL); 3. pthread_mutex_lock(&lock); 4. var = var + 1; 5. printf("Thread 1: %d\n", var); 6. pthread_mutex_unlock(&lock); 7. pthread_mutex_lock(&lock); 8. printf("Thread 0: %d\n", var); // 预期打印1! 9. pthread_mutex_unlock(&lock);
C标准本身没有明确定义并发模型,所以理论上符合标准的C编译器允许跨pthread_mutex_lock()调用将var的值缓存到寄存器中,导致步骤8打印0,但实际pthreads API的实现都会采取措施避免该问题。
问题
请问pthreads的实现是如何阻止编译器跨pthread_mutex_lock()调用将var的值缓存到寄存器的?请说明pthreads源码中向编译器传递相关信息的具体注解,可任选一种pthreads实现作答,例如面向aarch64架构编译的glibc。本题假设pthread_mutex_lock()定义在同一文件中,且编译器可证明其不会访问var。
解答
我们以glibc的aarch64架构实现为例说明:
- glibc的
pthread_mutex_lock、pthread_mutex_unlock等同步原语实现中,都会内嵌编译器内存屏障注解,最常见的形式是asm volatile ("" ::: "memory")。 - 该注解的作用是明确告知编译器:这段内嵌汇编代码可能读取或修改所有可见内存地址,因此编译器不能将这段代码前后的内存访问指令重排越过该屏障,也不能把屏障前缓存在寄存器中的变量值直接拿到屏障后使用,必须在屏障后重新从内存加载变量的最新值。
- 哪怕编译器可以证明
pthread_mutex_lock本身的代码没有显式访问var,这个内存屏障注解也会强制编译器丢弃之前缓存的var寄存器值,在调用pthread_mutex_lock之后重新从内存读取var的最新值,因此步骤8必然能读到线程1修改后的值1。 - 除了编译器层面的内存屏障,同步原语实现中还会加入对应架构的CPU内存屏障指令(aarch64上为
dmb/dsb指令),用来阻止CPU层面的指令重排,保证多核心的缓存一致性,避免其他核心的修改对当前核心不可见。
内容的提问来源于stack exchange,提问作者Jorge
相关产品推荐
相关产品推荐

