c語言並行程序設計之路(四)(barrier的實現和條件變量)


0.前言

接下來看共享內存編程的另一個問題:通過保證所有線程在程序中處於同一個位置來同步線程。這個同步點稱為barrier,翻譯為路障、柵欄等。只有所有線程都抵達此路障,線程才能繼續運行下去,否則會阻塞在路障處。

1.實現

1.1忙等待和互斥量

用忙等待和互斥量來實現路障比較直觀:使用一個由互斥量保護的共享計數器。當計數器的值表明每個線程都已經進入臨界區,所有線程就可以離開忙等待的狀態了。

/* Shared and initialized by the main thread*/
int counter; /*Initialize to 0*/
int thread_count;
pthread_mutex_t barrier_mutex;
...

void* Thread_work(...){
    ...
    /*Barrier*/
    pthread_mutex_lock(&barrier_mutex);
    counter++;
    pthread_mutex_unlock(&barrier_mutex);
    while(counter<thread_count);
    ...
}

缺點:

  • 線程處於忙等待循環時浪費了很多CPU周期,並且當程序中的線程數多過於核數時,程序的性能會直線下降。
  • 若想使用這種實現方式的路障,則有多少個路障就必須要有多少個不同的共享counter變量來進行計數。

1.2信號量

可以用信號量來實現路障,能解決采用忙等待和互斥量實現路障的方式里出現的問題。

/*Shared variables*/
int counter; /*Initialized to 0*/
sem_t count_sem; /*Initialized to 1*/
sem_t barrier_sem; /*Initialized to 0*/
...
    
void* Thread_work(...){
    ...
    /*Barrier*/
    sem_wait(&count_sem);
    if(counter == thread_count-1){
        counter = 0;
        sem_post(&count_sem);
        for(j = 0; j<thread_count-1; ++j)
            sem_post(&barrier_sem);
    }else{
        counter++;
        sem_post(&count_sem);
        sem_wait(&barrier_sem);
    }
    
}

在忙等待實現的路障中,使用了一個計數器counter來判斷有多少線程抵達了路障。在這里,采用了兩個信號量:count_sem,用於保護計數器;barrier_sem,用於阻塞已經進入路障的線程。

線程被阻塞在sem_wait不會消耗CPU周期,所以用信號量實現路障的方法比用忙等待實現的路障性能更佳。

如果想執行第二個路障,counter和count_sem可以重用,但是重用barrier_sem會導致競爭條件。

1.3條件變量

在pthreads中實現路障的更好方法是采用條件變量,條件變量是一個數據對象,允許線程在某個特定條件或事件發生前都處於掛起狀態。當條件或事件發生時,另一個線程可以通過信號來喚醒掛起的線程。一個條件變量總是與一個互斥量相關聯。

條件變量的一般使用方法與下面的偽代碼類似:

lock mutex;
if condition has occurred
    signal thread(s);
else{
    unlock the mutex and block;
    /*when thread is unblocked. mutex is relocked*/
}
unlock mutex;

Pthreads線程庫中的條件變量類型為pthread_cond_t。函數

int pthread_cond_signal(pthread_cond_t* cond_var_p /*in/out*/);

的作用是解鎖一個阻塞的線程,而函數

int pthread_cond_broadcast(pthread_cond_t* cond_var_p /*in/out*/);

的作用是解鎖所有被阻塞的線程。函數

 int pthread_cond_wait(
 	pthread_cond_t* cond_var_p /*in/out*/,
 	pthread_mutex_t* mutex_p	/*in/out*/);

的作用是通過互斥量mutex_p來阻塞線程,知道其他線程調用pthread_cond_signal或者pthread_cond_broadcast來解鎖它。當線程解鎖后,它重新獲得互斥量,所以實際上,pthread_cond_wait相當於按順序執行了以下的函數:

pthread_mutex_unlock(&mutex_p);
wait_on_signal(&cond_var_p);
pthread_mutex_lock(&mutex_p);

下面的代碼使用條件變量實現路障:

 /*Shared*/
 int counter=0;
pthread_mutex_t mutex;
pthread_cond_t cond_var;
...
    
void* Thread_work(...){
    ...
    /*Barrier*/
    pthread_mutex_lock(&mutex);
    counter++;
    if(counter == thread_count){
        counter == 0;
        pthread_cond_broadcast(&cond_var);
    }else{
        while(pthread_cond_wait(&cond_var, &mutex) != 0);
    }
    pthread_mutex_unlock(&mutex);
    ...
}

之所以將pthread_cond_wait語句放置在while語句內,是為了確保被掛起的線程是被broadcast函數或signal函數喚醒的,檢查其返回值是否為0,若不為0,則被其他事件解除阻塞的線程會再次執行該函數再次掛起。

與互斥量和信號量一樣,條件變量也應該初始化和銷毀。對應的函數是:

int pthread_cond_init(
	pthread_cond_t*			  	cond_p	/*out*/,
	const pthread_condattr_t* 	cond_attr_p	/*in*/);

int pthread_cond_destroy(pthread_cond_t* 	cond_p	/* in/out*/ );

2.參考資料

《並行程序設計導論》 4.8


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM