【发布时间】:2016-08-02 23:54:09
【问题描述】:
我有以下简单的 c++ 源代码:
#define CNTNUM 100000000
int iglbcnt = 0 ;
int iThreadDone = 0 ;
void *thread1(void *param)
{
/*
pid_t tid = syscall(SYS_gettid);
cpu_set_t set;
CPU_ZERO( &set );
CPU_SET( 5, &set );
if (sched_setaffinity( tid, sizeof( cpu_set_t ), &set ))
{
printf( "sched_setaffinity error" );
}
*/
pthread_detach(pthread_self());
for(int idx=0;idx<CNTNUM;idx++)
iglbcnt++ ;
printf(" thread1 out \n") ;
__sync_add_and_fetch(&iThreadDone,1) ;
}
int main(int argc, char **argv)
{
pthread_t tid ;
pthread_create(&tid , NULL, thread1, (void*)(long)1);
pthread_create(&tid , NULL, thread1, (void*)(long)3);
pthread_create(&tid , NULL, thread1, (void*)(long)5);
while( 1 ){
sleep( 2 ) ;
if( iThreadDone >= 3 )
printf("iglbcnt=(%d) \n",iglbcnt) ;
}
}
如果我运行它,答案肯定不会是 300000000,除非源使用 __sync_add_and_fetch(iglbcnt, 1 ) 而不是 iglbcnt++。
然后我尝试像 numactl -C 5 ./x.exe 一样运行,numactl 尝试亲和所有 3 个线程 1 以在核心 5 上运行,所以理论上,所有 3 个线程 1 中只有一个 可以在核心 5 上运行,并且由于 iglbcnt 是所有 thread1 的全局变量, 我希望答案是 300000000 ,不幸的是并非一直如此 得到 300000000 ,有时像 292065873 一样出来。
我猜为什么不总是得到 300000000 的原因是在核心 5 中进行上下文切换时, iglbcnt 的值仍然保留在 cpu 的存储缓冲区中,所以当调度程序运行另一个线程时,L1 缓存中 iglbcnt 的值将是 与 cpu core 5 的存储缓冲区中的值不同,导致答案 来 292065873 ,而不是 300000000 。
这只是实验,正如我所说的 __sync_add_and_fetch 将解决问题, 但我仍然想知道导致这个结果的细节。
编辑:
++igblcnt 和 igblcnt++ 生成相同的代码。
g++ --std=c++11 -S -masm=intel x.cpp ,(source ++iglbcnt) 以下代码来自 x.s :
.LFB11:
.cfi_startproc
push rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
mov rbp, rsp
.cfi_def_cfa_register 6
sub rsp, 32
mov QWORD PTR [rbp-24], rdi
call pthread_self
mov rdi, rax
call pthread_detach
mov DWORD PTR [rbp-4], 0
jmp .L2
.L3:
mov eax, DWORD PTR iglbcnt[rip]
add eax, 1
mov DWORD PTR iglbcnt[rip], eax
add DWORD PTR [rbp-4], 1
.L2:
cmp DWORD PTR [rbp-4], 99999999
jle .L3
mov edi, OFFSET FLAT:.LC0
call puts
lock add DWORD PTR iThreadDone[rip], 1
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE11:
.size _Z7thread1Pv, .-_Z7thread1Pv
.section .rodata
.LC1:
.string "iglbcnt=(%d) \n"
.text
编辑2:
for(int idx=0;idx<CNTNUM;idx++){
asm volatile("":::"memory") ;
iglbcnt++ ;
}
然后用 -O1 编译就可以了, 在这种情况下,添加编译器时内存屏障会有所帮助。
【问题讨论】:
-
你在 ++igblcnt 有一场比赛。所有线程都在读写这个变量。
-
增量是一个读-修改-写操作。如果两个线程读取相同的值,它们都将写回相同的增量值——丢失一个增量。你需要一个互锁的增量——对于 gcc 来说是 __sync_add_and_fetch。
-
@David,是的,使用 gcc 4.8.2
-
我可以问什么 CPU?
-
@David ,我知道你的意思,我的问题是我亲和所有线程都运行在 cpu core5 中,所以这三个线程中只有一个可以执行 iglbcnt++ ,我猜这个结果的核心原因来自cpu的设计,我猜是和store buffer有关吧。
标签: c++ linux multithreading cpu