zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    阻止事件的默认行为,例如click <a>后的跳转~
    阻止事件冒泡
    IE67不兼容display:inline-block,CSS hack解决
    IE678不兼容CSS3 user-select:none(不可复制功能),需要JS解决
    JS数组常用方法总结
    json 只能用 for-in 遍历
    用实例的方式去理解storm的并发度
    OpenLDAP 搭建入门
    kafka api的基本使用
    kafka基本介绍
  • 原文地址:https://www.cnblogs.com/wangzhebufangqi/p/14227186.html
Copyright © 2011-2022 走看看