簡體   English   中英

在單個內核上運行的多個線程如何進行數據競爭?

[英]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將解決問題,但這只是實驗,但我仍然想知道導致此結果的細節。

編輯:

++igblcntigblcnt++產生相同的代碼。

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM