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