我有以下简单的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个thread1绑定到核心5运行,所以从理论上讲,所有3个thread1中只有一个 可以在核心5运行,因为iglbcnt是对所有thread1的全局变量, 我希望答案是3亿,不幸的是它不是所有的时间 获得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
Edit2:
for(int idx=0;idx<CNTNUM;idx++){
asm volatile("":::"memory") ;
iglbcnt++ ;
}
然后通过-O1编译它将正常工作, 在这种情况下,添加编译器时间内存屏障会有所帮助。
答案 0 :(得分:2)
igblcnt ++是一个加载,添加,存储序列。这是在没有同步的情况下执行的,因此线程(即使在同一个核心上进行调度)也会有竞争,因为每个线程都有自己的寄存器上下文。 igblcnt上的__sync_add_and_fetch指令将解决竞争问题。
进入核心寄存器的负载发生然后线程被切换(它的寄存器被保存)另一个线程读取相同的值并递增并将其存储回存储器(可能是数百个增量)然后第一个线程被切换与其陈旧的值,增加和存储 - 潜在地减少数千到数百万的增量(如您所见)。
答案 1 :(得分:0)
在一个处理器上运行的线程如果被抢占式调度则可以进行数据竞争,这意味着可以在触发线程上下文切换的任何时刻发生中断。然后,线程必须使用互斥机制,如互斥对象,或原子指令(以及精心设计的算法)。
单个处理器上的协作调度线程可以隐式地避免数据争用。在单个处理器上的协作线程下,一个线程执行,直到它显式调用某个切换上下文的函数。任何不调用此类函数的代码都不受其他线程的干扰。