如何确保多核心上两个线程的O1、O2操作精准并行执行?
核心间线程高精度同步执行问题
我编写了一个C程序,除主线程外创建了两个线程T1和T2。T1执行函数f1调用操作O1,T2执行函数f2调用操作O2,代码如下:
原始代码
线程函数f1
void* f1() { O1(); var = 0; }
线程函数f2
void* f2() { O2(); var = 1; }
主函数
int main(int argc, char **argv){ pthread_t t1, t2; int var; pthread_create(&t1, NULL, &f1, NULL); pthread_create(&t2, NULL, &f2, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); printf("var = %d\n", var); return 0; }
程序目的是通过线程结束后var的值判断O1和O2哪个操作更快,这要求O1()和O2()在两个不同物理核心上几乎同时执行(仅允许几个周期的误差)。我该如何实现这一点?
编辑后的尝试(未成功)
根据建议,我修改了f1()和f2(),通过读取时间戳同步O1()和O2()的执行:
修改后的f1
void* f1() { t1 = rdtsc(); while(t1 != 0){ t1 = rdtsc(); } printf("t1 = %d\n", t1); O1(); var = 0; }
修改后的f2
void* f2() { t2 = rdtsc(); while(t2 != 0){ t2 = rdtsc(); } printf("t2 = %d\n", t2); O2(); var = 1; }
但控制台中t2的输出远晚于t1,这说明通过rdtsc循环到0的方式并未实现O1()和O2()的同步执行,且线程屏障无法满足我所需的同步粒度。
解决方案
要实现周期级的核心间线程同步,需要结合CPU同步指令、线程绑定和精确时间戳控制,避免调度延迟和指令重排的干扰。以下是具体实现方案:
1. 先绑定线程到指定物理核心
必须将T1和T2分别绑定到不同物理核心,防止调度器迁移线程导致的延迟。用sched_setaffinity实现:
#include <sched.h> // 绑定线程到指定核心 void bind_to_core(pthread_t thread, int core_id) { cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(core_id, &cpuset); pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuset); } // 在main中创建线程后调用 bind_to_core(t1, 0); // 绑定到核心0 bind_to_core(t2, 1); // 绑定到核心1
2. 基于全局时间戳阈值的同步方案
设置全局目标时间戳,让两个线程先进入就绪状态,再同时等待时间戳达到阈值后执行操作,确保启动时机一致。
完整可运行代码
#include <pthread.h> #include <stdio.h> #include <sched.h> #include <stdint.h> // 全局同步变量,volatile防止编译器优化 volatile uint64_t target_ts = 0; volatile int ready_count = 0; volatile int var; // 测试操作O1、O2,替换为你的实际逻辑 void O1() { /* 你的操作 */ } void O2() { /* 你的操作 */ } // 读取序列化时间戳,保证指令顺序 static inline uint64_t rdtscp(void) { uint32_t lo, hi; __asm__ __volatile__ ("rdtscp" : "=a"(lo), "=d"(hi) : : "%rcx"); return ((uint64_t)hi << 32) | lo; } void bind_to_core(pthread_t thread, int core_id) { cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(core_id, &cpuset); pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuset); } void* f1(void* arg) { // 原子递增就绪计数,确保线程安全 __sync_fetch_and_add(&ready_count, 1); // 等待主线程设置目标时间戳 while (target_ts == 0); // 循环等待直到时间戳达标 while (rdtscp() < target_ts); // 执行操作 O1(); var = 0; return NULL; } void* f2(void* arg) { __sync_fetch_and_add(&ready_count, 1); while (target_ts == 0); while (rdtscp() < target_ts); O2(); var = 1; return NULL; } int main(int argc, char **argv){ pthread_t t1, t2; pthread_create(&t1, NULL, f1, NULL); pthread_create(&t2, NULL, f2, NULL); bind_to_core(t1, 0); bind_to_core(t2, 1); // 等待两个线程都进入就绪状态 while (ready_count < 2); // 设置目标时间戳:当前时间+100000周期,预留足够时间让线程进入等待循环 target_ts = rdtscp() + 100000; pthread_join(t1, NULL); pthread_join(t2, NULL); printf("var = %d\n", var); return 0; }
3. 关键细节说明
volatile关键字:确保全局变量在多线程间的可见性,避免编译器优化导致的读取旧值问题。__sync_fetch_and_add原子操作:保证ready_count的递增线程安全,不会出现计数错误。- 足够的等待周期:设置
target_ts时额外加100000周期,是为了让两个线程都能进入等待循环,避免主线程设置目标后某个线程还未就绪就直接执行操作。 - 避免IO干扰:测试阶段不要在同步后的代码中加入
printf等IO操作,这类操作延迟极大,会完全破坏测试精度。
4. 可选增强:内存屏障
如果需要更强的内存序保证,可在等待循环后加入mfence指令,确保后续指令不会被重排到等待循环之前:
while (rdtscp() < target_ts); __asm__ __volatile__ ("mfence" ::: "memory"); O1();
内容的提问来源于stack exchange,提问作者Sathvik Swaminathan
相关产品推荐
相关产品推荐

