![](/img/trans.png)
[英]How would one avoid race conditions from multiple threads of a server sending data to a client? C++
[英]How can multiple threads, running on a single core, have a data race?
我有以下簡單的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的globar vars,我希望答案是3億,不幸的是並非每次都得到3億,有時像292065873那樣出來。
我想為什么不總是獲得300000000的原因是,在核心5中進行上下文切換時,iglbcnt的值仍保留在cpu的存儲緩沖區中,因此當調度程序運行另一個線程時,L1緩存中iglbcnt的值將與cpu中的值不同核心5的存儲緩沖區,導致答案來自292065873,而不是300000000。
正如我所說的__sync_add_and_fetch將解決問題,但這只是實驗,但我仍然想知道導致此結果的細節。
編輯:
++igblcnt
和igblcnt++
產生相同的代碼。
g ++ --std = c ++ 11 -S -masm = intel x.cpp,(源++ iglbcnt)以下代碼來自xs:
.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 ++是加載,添加,存儲序列。 這是在沒有同步的情況下執行的,因此線程(即使調度在同一內核上)也會產生競爭,因為每個線程都有自己的寄存器上下文。 igblcnt上的__sync_add_and_fetch指令將解決該競爭。
加載到內核的寄存器中,然后將線程切換出(保存寄存器),另一個線程讀取相同的值並遞增,並將其存儲回內存(可能數百次遞增),然后將第一個線程與其線程一起切入已過時的值(已增加並存儲)-可能損失數千到數百萬的增量(如您所見)。
如果搶先調度在一個處理器上運行的線程,則可能會引起數據爭用,這意味着可以在觸發線程上下文切換的任何時刻發生中斷。 然后,線程必須使用互斥機制,例如互斥對象或原子指令(以及精心設計的算法)。
單個處理器上的協作調度線程避免了隱式的數據爭用。 在單個處理器上的協作線程下,一個線程將執行直到它顯式調用某些切換上下文的函數為止。 任何不調用該函數的代碼都不會受到其他線程的干擾。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.