linux下多线程之生成者与消费者模型(互斥,读写锁,条件变量)

来源:互联网 发布:招聘网络平面设计师 编辑:程序博客网 时间:2024/05/09 09:19

线程间的同步方法大体可分为两类:用户模式和内核模式。顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态,而用户模式就是不需要切换到内核态,只在用户态完成操作。

用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。

内核模式下的方法有:事件,信号量,互斥量。

下面我们来分别看一下这些方法:

一、互斥锁或互斥量(mutex)

下面是用互斥量来解决生产者和消费者问题。为了现集中体现互斥量这个概念(就是一次只能有一个线程访问,其他线程阻塞),我们先简化一下问题:缓冲区或者仓库无限大(生产者和消费者都可以生产和消费产品,而且产品初始化时候数量就是无限多,这里我们主要体现),只有一个生产者和一个消费者,我们这个时候就可以把缓冲区设置为一个互斥量,一次要么生产者要么消费者霸占它。

·  初始化锁。在Linux下,

线程的互斥量数据类型是pthread_mutex_t。在使用前,要对它进行初始化。
静态分配:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
动态分配:int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutex_attr_t *mutexattr);

·  加锁。对共享资源的访问,要对互斥量进行加锁,如果互斥量已经上了锁,调用线程会阻塞,直到互斥量被解锁。
int pthread_mutex_lock(pthread_mutex *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);

·  解锁。在完成了对共享资源的访问后,要对互斥量进行解锁。
int pthread_mutex_unlock(pthread_mutex_t *mutex);

·  销毁锁。锁在是使用完成后,需要进行销毁以释放资源。
int pthread_mutex_destroy(pthread_mutex *mutex);

    #include <stdio.h>      #include <pthread.h>            #define LOOP_COUNT 5            //生产者和消费者各自循环次数      pthread_mutex_t mutex;          //定义一个全局互斥量,在不同函数中                                      //初始化和使用            void *producer( void *arg );    //生产者线程      void *consumer( void *arg );    //消费者线程            int main(int argc , char *argv[]){          pthread_t thrd_prod , thrd_cons;                pthread_mutex_init( &mutex , NULL );    //初始化互斥量                //创建生产者和消费者线程          if( pthread_create( &thrd_prod , NULL, producer ,                      NULL ) != 0 )              oops( "thread create failed." );          sleep(1);                               //保证生产者线程先运行                if( pthread_create( &thrd_cons , NULL, consumer ,                      NULL ) != 0 )              oops( "thread create failed." );                //等待线程结束          if( pthread_join( thrd_prod , NULL ) != 0 )              oops( " wait thread failed.");          if( pthread_join( thrd_cons , NULL ) != 0 )              oops( " wait thread failed.");                pthread_mutex_destroy( &mutex );        //关闭互斥量          return 0;      }            void *producer( void *arg){          int count = 0 ;             //循环计数                while( count++ < LOOP_COUNT ){              pthread_mutex_lock( &mutex );   //加锁                    //成功占有互斥量,接下来可以对缓冲区(仓库)进行生产              //操作              printf( " producer put a product to buffer.\n");              sleep(3);               //休眠3秒, 便于程序观察                    pthread_mutex_unlock( &mutex ); //解锁              sleep(1);               //休眠一秒,防止它又马上占据锁          }      }      void *consumer( void *arg ){          int count = 0 ;             //循环计数                while( count++ < LOOP_COUNT ){      //      sleep(2);               //休眠一秒, 便于程序观察              pthread_mutex_lock( &mutex );   //加锁                    //成功占有互斥量,接下来可以对缓冲区(仓库)进行取出              //操作              printf( " consumer get a product from buffer.\n");                    pthread_mutex_unlock( &mutex ); //解锁              sleep(1);               //休眠一秒,防止它又马上占据锁          }      }  

二、读写锁

读写锁也叫做共享-独占锁,当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。

接下来我们改变一下生产者消费者问题:现在缓冲区或者仓库无限大(生产者和消费者都可以生产和消费产品,而且产品初始化时候数量就是无限多,这里我们主要体现),只有一个生产者(读写锁也可以应用到多个生产者问题),但有多个消费者, 我们这个时候就可以把为生产者设置一个写锁,为每个消费者设置一个读锁。

  1. 1.初始化读写锁。

    #include <pthread.h>
    int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,constpthread_rwlockattr_t *restrict attr);

    2.加锁。要在读模式下锁定读写锁,需要调用pthread_rwlock_rdlock要在写模式下锁定读写锁,需要调用pthread_rwlock_wrlock当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是如果线程希望以写模式对此锁进行加锁,它必须阻塞直到所有的线程释放读锁

    intpthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

    intpthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

    3.解锁。在完成了对共享资源的访问后,要对读写锁进行解锁。
    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

    4.销毁锁。在释放读写锁占用的内存之前,需要调用pthread_rwlock_destroy做清理工作如果pthread_rwlock_init为读写锁分配了资源,pthread_rwlock_destroy将释放这些资源。如果在调用pthread_rwlock_destroy之前就释放了读写锁占用的内存空间,那么分配给这个锁的资源就丢失了。
    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

三、条件变量(cond)


与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。条件变量分为两部分:条件和变量。条件本身是由互斥量保护的。线程在改变条件状态前先要锁住互斥量。条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。

1.初始化条件变量。
静态态初始化,pthread_cond_t cond = PTHREAD_COND_INITIALIER;
动态初始化,int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);

2.等待条件成立。释放锁,同时阻塞等待条件变量为真才行。timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait)
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex
*mutex,consttimespec *abstime);

3.激活条件变量。pthread_cond_signal,pthread_cond_broadcast(激活所有等待线程)
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond); //
解除所有线程的阻塞

4.清除条件变量。无线程等待,否则返回EBUSY
int pthread_cond_destroy(pthread_cond_t *cond); 

   接下来我们又改变一下生产者消费者问题:现在缓冲区或者仓库大小为BUFSIZE,只有一个生产者和一个消费者(其实也适用于多个生产者和消费者), 我们这个时候就可以把缓冲区设置为一个互斥量,一次要么生产者要么消费者霸占它。但接下来处理方式与互斥量有所不同:假如生产者成功占据锁(缓冲区),这时它不能马上开始往里面生产东西,要先判断缓冲区是不是满的,如果缓冲区满了,那么生产者就会把自己放到等待条件的线程列表上,然后对互斥量进行解锁,这是一个原子操作。如果缓冲区不满则可以生产产品,然后给消费者发送notempty信号,表示缓冲区有产品了, 你可以yy了。然后解锁互斥量。假如是消费者成功占据锁(缓冲区),同样它要检查缓冲区是不是空的,如果空,那么消费者就会把自己放到等待条件的线程列表上,然后对互斥量进行解锁。如果不空,消费者开始yy,然后给生产者发送nofull信号, 表示缓冲区有位置可以生产了, 你快生产吧。然后解锁互斥量。就这样,生产者消费者和谐同步工作着

/************   条件变量配合互斥锁实现   ************/#define MUTEX_COND_MAX 5  pthread_cond_t notfull     = PTHREAD_COND_INITIALIZER;    /* 是否队满 */ pthread_cond_t notempty    = PTHREAD_COND_INITIALIZER;    /* 是否队空 */ pthread_mutex_t task_mutex = PTHREAD_MUTEX_INITIALIZER;  wm_int mutex_cond_top    = 0;  wm_int mutex_cond_bottom = 0;wm_void* mutex_cond_produce(wm_void* arg)  {      wm_int i;        for( i = 0; i < MUTEX_COND_MAX*2; i++)    {        /* 锁定互斥锁 */        pthread_mutex_lock(&task_mutex);        while( (mutex_cond_top+1)%MUTEX_COND_MAX == mutex_cond_bottom )        {              printf("full! producer is waiting\n");            /* 等待队不满 */             pthread_cond_wait(¬full, &task_mutex);        }            mutex_cond_top = (mutex_cond_top+1) % MUTEX_COND_MAX;          printf("produce thread(%d)now top is %d\n", pthread_self(), mutex_cond_top);        /* 发出队列非空的消息 */        pthread_cond_signal(¬empty);         /* 解锁互斥锁 */        pthread_mutex_unlock(&task_mutex);        printf("produce thread(%d)now sleep\n", pthread_self());        while_select_seconds_sleep(1);    }        return (void*)1;}  wm_void* mutex_cond_consume(wm_void* arg)  {      wm_int i;        for ( i = 0; i < MUTEX_COND_MAX*2; i++)      {          while_select_seconds_sleep(1);        pthread_mutex_lock(&task_mutex);        while ( mutex_cond_top%MUTEX_COND_MAX == mutex_cond_bottom)          {            printf("empty! consumer is waiting\n");            /* 等待队不空 */            pthread_cond_wait(¬empty, &task_mutex);        }        mutex_cond_bottom = (mutex_cond_bottom+1) % MUTEX_COND_MAX;          printf("produce thread(%d) now bottom is %d\n", pthread_self(), mutex_cond_bottom);        /* 发出队不满的消息 */        pthread_cond_signal(¬full);          pthread_mutex_unlock(&task_mutex);        printf("produce thread(%d)now sleep\n", pthread_self());        while_select_seconds_sleep(1);    }      return (void*)2;  }#if 0#define MAX_THREAD_NUM      4wm_ret mutex_cond_main(wm_void)  {      wm_int i = 0;    pthread_t thid[MAX_THREAD_NUM] = {0};    wm_int ret[MAX_THREAD_NUM] = {0};    for( i = 0; i < MAX_THREAD_NUM; i++ ) {        if( pthread_create(&thid[i], NULL, mutex_cond_produce, NULL) != 0 ) {            WM_DEBUG_PRINT( "thread create failed." );            exit();        }            }    for( i = 0; i < MAX_THREAD_NUM; i++ ) {        pthread_join(thid[i], (void**)&ret);    }    return 0;}#endif wm_ret mutex_cond_main(wm_void)  {     wm_int ret1;    wm_int ret2;    wm_int ret3;    wm_int ret4;    pthread_t thid1;    pthread_t thid2;    pthread_t thid3;    pthread_t thid4;    if( pthread_create(&thid1, NULL, mutex_cond_produce, NULL) != 0 ) {        WM_DEBUG_PRINT( "thread create failed." );      }    if( pthread_create(&thid2, NULL, mutex_cond_consume, NULL) != 0 ) {        WM_DEBUG_PRINT( "thread create failed." );      }    if( pthread_create(&thid3, NULL, mutex_cond_produce, NULL) != 0 ) {        WM_DEBUG_PRINT( "thread create failed." );      }    if( pthread_create(&thid4, NULL, mutex_cond_consume, NULL) != 0 ) {        WM_DEBUG_PRINT( "thread create failed." );      }    pthread_join(thid1, (void**)&ret1);    pthread_join(thid2, (void**)&ret2);    pthread_join(thid3, (void**)&ret3);    pthread_join(thid4, (void**)&ret4);    return 0;} 


0 0