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.初始化读写锁。
#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;}
- linux下多线程之生成者与消费者模型(互斥,读写锁,条件变量)
- linux系统中多线程同步之互斥变量、读写锁、条件变量
- 【线程的同步与互斥 (互斥量 条件变量 信号量)】生产者与消费者模型
- Linux 线程同步机制:互斥、读写锁、条件变量
- Linux--线程同步与互斥之条件变量
- linux下多线程同步机制之信号量、互斥量、读写锁、条件变量
- Linux多线程间同步与互斥---条件变量(Conditoin Variable)
- 多线程学习系列四 生产者和消费者 互斥所和条件变量实现
- QThread中的互斥、读写锁、信号量、条件变量
- QThread中的互斥、读写锁、信号量、条件变量
- QThread中的互斥、读写锁、信号量、条件变量
- QThread中的互斥、读写锁、信号量、条件变量
- QThread中的互斥、读写锁、信号量、条件变量
- QThread中的互斥、读写锁、信号量、条件变量
- QThread中的互斥、读写锁、信号量、条件变量
- QThread中的互斥、读写锁、信号量、条件变量
- linux c++之互斥变量和条件变量
- Linux多线程消费者和生产者模型实例(互斥锁和条件变量使用)
- 263. Ugly Number
- [容斥原理] BZOJ 2839 集合计数
- OC中与copy有关的那些事 一 (copy与声明NSString属性 : strong/copy 的关系)
- 广告行业eCPM概念
- linux shell 获取当前正在执行脚本的绝对路径
- linux下多线程之生成者与消费者模型(互斥,读写锁,条件变量)
- onCreateContextMenu用法
- 接口三
- swift3.0 基本数据类型
- 【老戴说镜头】怎样才算一颗好的镜头
- apache日志分析简介
- 记录自己的成长
- 一位五年程序猿的小博客开通啦
- 设计模式——迭代器模式(Iterator Pattern)