为何页故障未使线程延后执行?同核线程执行顺序不符预期
问题描述
我编写了如下代码,在file.c中故意让其中一个线程触发页故障。我使用rdtscp()作为内存屏障,确保打印语句在加载操作完成后执行。由于线程t2会触发页故障,我预期t1的打印语句会先执行。我通过taskset -c 10 ./file让两个线程运行在同一核心上,但实际看到t2先输出打印语句,请问这是什么原因?
代码实现
util.c
#include "util.h" // to use as a fence() instruction extern inline __attribute__((always_inline)) CYCLES rdtscp(void) { CYCLES cycles; asm volatile ("rdtscp" : "=a" (cycles)); return cycles; } // initialize address void init_ram_address(char* FILE_NAME){ char *filename = FILE_NAME; int fd = open(filename, O_RDWR); if(fd == -1) { printf("Could not open file .\n"); exit(0); } void *file_address = mmap(NULL, DEFAULT_FILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, fd, 0); ram_address = (int *) file_address; } // initialize address void init_disk_address(char* FILE_NAME){ char *filename = FILE_NAME; int fd = open(filename, O_RDWR); if(fd == -1) { printf("Could not open file .\n"); exit(0); } void *file_address = mmap(NULL, DEFAULT_FILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); disk_address = (int *) file_address; }
file.c
#include "util.h" void *f1(); void *f2(); pthread_barrier_t barrier; pthread_mutex_t mutex; int main(int argc, char **argv) { pthread_t t1, t2; // in ram init_ram_address(RAM_FILE_NAME); // in disk init_disk_address(DISK_FILE_NAME); pthread_create(&t1, NULL, &f1, NULL); pthread_create(&t2, NULL, &f2, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); return 0; } void *f1() { rdtscp(); int load = *(ram_address); rdtscp(); printf("Expecting this to be run first.\n"); } void *f2() { rdtscp(); int load = *(disk_address); rdtscp(); printf("Expecting this to be run second.\n"); }
问题分析与解答
出现这种现象的核心原因有三个:
线程启动无同步机制
你定义了pthread_barrier_t但未初始化、未使用,两个线程的启动时间完全由操作系统调度器决定,不遵循创建顺序。t2可能比t1更早被调度执行,哪怕绑定到同一核心也无法保证执行顺序。页故障处理的不确定性
如果目标文件的页已经被操作系统缓存,t2的加载操作不会触发真正的磁盘IO,页故障处理会在极短时间内完成——甚至比t1的加载+打印流程更快,导致t2的打印先输出。想要确保触发真实磁盘IO的页故障,可在运行程序前执行sync; echo 3 > /proc/sys/vm/drop_caches(需要root权限)清除文件缓存。对
rdtscp屏障作用的误解rdtscp仅能保证当前线程内,它之前的指令全部执行完成后,后续指令才会开始,但无法干预线程间的调度顺序。它只是确保t2的打印一定在自身加载操作完成后执行,不能控制t1和t2之间的执行先后。
解决方法
- 添加线程同步:初始化并使用屏障,确保两个线程同时开始执行:
在main中添加pthread_barrier_init(&barrier, NULL, 2);,并在t1和t2函数开头添加pthread_barrier_wait(&barrier);。 - 强制清除缓存:运行程序前执行缓存清除命令,确保t2触发真实磁盘页故障。
内容的提问来源于stack exchange,提问作者Sathvik Swaminathan
相关产品推荐
相关产品推荐

