c语言并行程序设计之路(四)(barrier的实现和条件变量)

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

...

}

缺点:

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

🌟 相关推荐

穿越火线枪战王者斯太尔天使什么时候出 斯太尔天使多少钱
DirectStorage怎么开启?支持的游戏+实机测试分享
beat365上不去

DirectStorage怎么开启?支持的游戏+实机测试分享

📅 08-02 👁️ 1155
CSGO及CS新版本投掷物数据对比:烟雾弹是否应削弱?