2012-12-22 22 views
2

我寫這段代碼線程不能指望,給人錯誤的結果

#include <stdio.h>  /* Input/Output */ 
#include <stdlib.h>  /* General Utilities */ 
#include <pthread.h> /* POSIX Threads */ 
unsigned int cnt=0; /*Count variable%*/ 
const int NITERS=1000; 
void count() 
{ 
    int i=0; 
    for(i=0; i<NITERS; i++) 
    { 
     cnt++; 
    } 
    pthread_exit(0); 
} 
int main() 
{ 
    pthread_t tid1,tid2; 
    /* create threads 1 and 2 */ 
    pthread_create(&tid1,NULL,count,NULL); 
    pthread_create(&tid2,NULL,count,NULL); 
    /* Main block now waits for both threads to terminate, before it exits 
     If main block exits, both threads exit, even if the threads have not 
     finished their work */ 
    pthread_join(tid1,NULL); 
    pthread_join(tid2,NULL); 
    if(cnt!=(unsigned)NITERS*2) 
    { 
     printf("BOOM! cnt=%d, it should be %d\n",cnt,NITERS*2); 
    } 
    else 
    { 
     printf("OK! cnt=%d\n",cnt); 
    } 
    exit(0); 
} 

,並表現出這樣的結果。 Result to the code

有些時候它會變成2000,但大多數時候它會給出的結果少於2000.你能解釋一下爲什麼會發生這種情況或背後的原因是什麼?如何解決它。你的答案和理由肯定會有很大的幫助。

回答

3

unsigned int cnt=0;是可共享的線程和操作++不是自動增加cnt。兩個線程可能會讀取相同的值cnt並增加,並覆蓋cnt。您需要應用一些像信號量或互斥體這樣的併發控制機制。


如果將使用下面的命令(假設代號爲thread1.c

~$ gcc thread.c -lpthread -S 

輸出組件代號爲thread1.s拆卸代碼。

你WIL找到cnt++是在代碼水平低不止一個指令:

movl $0, -12(%ebp) 
    jmp .L2 
.L3: 
    movl cnt, %eax 
    addl $1, %eax 
    movl %eax, cnt 
    addl $1, -12(%ebp) 
.L2: 
    movl NITERS, %eax 

(1)cnt拳頭舉到%eax
(2)再加入一到%exc
(3)將%eax轉換爲cnt返回

並且由於此行之間的線程上下文切換,相同的值cnt被多個線程。因此cnt++不是原子的。

注:全局變量是線程共享像cnt和局部變量一樣i您在count()聲明是線程特定的。


我修改了你的代碼並使用信號量強加併發控制,現在它可以正常工作。

只有修改後的代碼所示

#include <pthread.h> /* POSIX Threads */ 
#include <semaphore.h> 
unsigned int cnt=0; /*Count variable%*/ 
const int NITERS=1000; 

sem_t mysem; 

void count() 
{ 
    int i=0; 
    for(i=0; i<NITERS; i++) 
    { 
     sem_wait(&mysem); 
     cnt++; 
     sem_post(&mysem); 
    } 
    pthread_exit(0); 
} 
int main() 
{ 
    if (sem_init(&mysem,0,1)) { 
    perror("init"); 
    } 
    // rest of your code 
} 

這將工作好!一些例子:

[email protected]:~$ ./thread 
OK! cnt=2000 
[email protected]:~$ ./thread 
OK! cnt=2000 
[email protected]:~$ ./thread 
OK! cnt=2000 
1

遞增運算符通常通過讀 - 修改 - 寫,這是非原子實現。

跨線程非原子讀 - 修改 - 寫有時可以做到這一點:

Thread 1: Thread 2:  count 
Read count ...   1 
Add 1  Read count 1 
Write count Add 1   2 
...   Write count 2 

在計數導致可能低於預期。

如果您將訪問跨多個線程的共享資源,則需要使用某種線程感知鎖定機制(如互斥鎖)來保護它。

1

您的2個線程訪問未經保護的共享資源,因此競爭條件適用。增加操作不是原子操作,所以實際上你可以在機器操作方面是這樣的:

Thread 1     Thread 2 
Load value of cnt   
          Load value of cnt 
Increase value of cnt  
Write value of cnt   Increase value of cnt 
          Write value of cnt 

注意的是,儘管這兩個線程都增加了cnt,它實際上只有1 如果增加你希望結果是確定性的,你需要保護共享資源(cnt),例如通過在訪問之前鎖定它。

1

你有一個競爭條件problem.More信息(我知道它談論視覺基本,只是跳過這些東西)here
爲了解決這個問題,你需要一個互斥體,其聲明爲全局變量:

pthread_mutex_t mux; 

初始化:

pthread_mutex_init(&mux,NULL); 

然後用它來讀取共享變量:

void count() 
{ 
    int i=0; 
    for(i=0; i<NITERS; i++) 
    { 
     pthread_mutex_lock(&mux); 
     cnt++; 
     pthread_mutex_unlock(&mux); 
    } 
    pthread_exit(0); 
} 

所有這是因爲有兩個線程遞增相同的變量,他們在遞增它之前取出變量並將它們放入寄存器中。所以他們認爲他們正在讀取的並不是唯一的:每個線程都有自己的副本,因此每個線程都可以忽略另一個線程的更改,直到他們有效地將其寫入內存中的地址爲止。
NB如果您希望線程以有序的方式更新變量(即:線程1在沒有被中斷的情況下計數到NITERS,當線程2開始計數時),您必須在for之前鎖定該互斥量。