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 ... } 缺点: 线程处于忙等待循环时浪费了很多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 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