http://blog.csdn.net/big_bit/article/details/51356393
在进行多线程编程时,难免还要碰到两个问题,那就线程间的互斥与同步:
线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。
线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步(下文统称为同步)。
生产者消费者问题就是一个著名的线程同步问题,该问题描述如下:有一个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个具有多个缓冲区的缓冲池,生产者将它生产的产品放入一个缓冲区中,消费者可以从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个已经放入产品的缓冲区中再次投放产品。
关于线程同步和互斥的详细说明可以看: http://blog.csdn.net/big_bit/article/details/51356381这篇文章
线程间的同步方法大体可分为两类:用户模式和内核模式。顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态,而用户模式就是不需要切换到内核态,只在用户态完成操作。
用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。内核模式下的方法有:事件,信号量,互斥量。下面我们来分别看一下这些方法:
一、互斥锁或互斥量(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); //休眠一秒,防止它又马上占据锁
- }
- }
结果如下:
从结果可以看到,当生产者和消费者成功lock互斥量时,另一个就阻塞等待。
二、读写锁
读写锁也叫做共享-独占锁,当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。
接下来我们改变一下生产者消费者问题:现在缓冲区或者仓库无限大(生产者和消费者都可以生产和消费产品,而且产品初始化时候数量就是无限多,这里我们主要体现),只有一个生产者(读写锁也可以应用到多个生产者问题),但有多个消费者, 我们这个时候就可以把为生产者设置一个写锁,为每个消费者设置一个读锁。
- 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);
- #include <stdio.h>
- #include <pthread.h>
-
- #define LOOP_COUNT 2 //生产者和消费者各自循环次数
- #define LOOP_THRD 5 //消费者线程个数
- pthread_rwlock_t rwlock; //定义一个全局读写锁,在不同函数中
- //初始化和使用
-
- void *producer( void *arg ); //生产者线程
- void *consumer( void *arg ); //消费者线程
-
- int main(int argc , char *argv[]){
- int thrd_num ,thrd_id[LOOP_THRD] ;
- pthread_t thrd_prod , thrd_cons[LOOP_THRD];
-
- pthread_rwlock_init( &rwlock , NULL ); //初始化互斥量
-
- //创建一个生产者和多个消费者线程
- if( pthread_create( &thrd_prod , NULL, producer ,
- NULL ) != 0 )
- oops( "thread create failed." );
-
- for( thrd_num = 0 ; thrd_num < LOOP_THRD; thrd_num++ ){
- thrd_id[thrd_num] = thrd_num; //线程id,注意线程共享变量
- if( pthread_create( &thrd_cons[thrd_num], NULL, consumer
- , <span style="background-color: rgb(255, 0, 0);">(void *)( thrd_id+thrd_num)</span> ) != 0 )
- oops( "thread %d create failed." , thrd_num );
- }
-
- //等待线程结束
- if( pthread_join( thrd_prod , NULL ) != 0 )
- oops( " wait thread failed.");
- for( thrd_num = 0 ; thrd_num < LOOP_THRD; thrd_num++ ){
- if( pthread_join( thrd_cons[thrd_num] , NULL ) != 0 )
- oops( " wait thread %d failed." , thrd_num);
- // printf("wait %d thread.\n" , thrd_num);
- }
- pthread_rwlock_destroy( &rwlock ); //关闭互斥量
- return 0;
- }
-
- void *producer( void *arg){
- int count = 0 ; //循环计数
-
- while( count++ < LOOP_COUNT ){
- printf( "producer try to lock wrlock.\n");
- pthread_rwlock_wrlock( &rwlock ); //加锁
-
- //成功占有互斥量,接下来可以对缓冲区(仓库)进行生产
- //操作
- printf( "producer lock successful, producer put a product to buffer.\n");
-
- /*
- 休眠3秒, 便于程序观察,可以看到
- 其他读取线程不能占据锁而阻塞
- */
- sleep(3);
- printf("prducer finished ,unlock wrlock.\n");
- pthread_rwlock_unlock( &rwlock ); //解锁
- sleep(1); //休眠一秒, 防止马上又占据写锁
- }
- }
- void *consumer( void *arg ){
- int count = 0 ; //循环计数
- int thrd_id = *( ( int*)arg );
-
- // printf( "consumer %d ,%#x . \n" , thrd_id ,arg);
- while( count++ < LOOP_COUNT ){
- // sleep( thrd_id+1 ); //休眠一秒, 便于程序观察
- printf( "consumer try to lock rdlock.\n" );
- pthread_rwlock_rdlock( &rwlock ); //加锁
-
- //成功占有互斥量,接下来可以对缓冲区(仓库)进行取出
- //操作
- printf( " consumer locked successful ,consumer %d get a product from buffer."
- "\n" , thrd_id);
- /*
- 休眠3秒, 便于程序观察,可以看到
- 其他读取线程能占据读锁
- */
- sleep(3);
- printf("consumer finished ,unlock rdlock.\n");
- pthread_rwlock_unlock( &rwlock ); //解锁
- sleep(thrd_id+1); //休眠一秒, 防止马上又占据读锁
- }
- }
结果如下:

可以看到当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是如果线程希望以写模式对此锁进行加锁,它必须阻塞直到所有的线程释放读锁。虽然读写锁的实现各不相同,但当读写锁处于读模式锁住状态时,如果有另外的线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求(貌似在程序里面没有体现出来)。这样可以避免读模式锁长期占用,而等待的写模式锁请求一直得不到满足。
另外我要说明的一点就是,传递参数 arg 为(void *)( thrd_id+thrd_num),我一开始并没有定义一个数组thrd_cons[LOOP_THRD]来存储线程编号的, 而是直接传thrd_num的地址,但通过在线程
int thrd_id = *( ( int*)arg );
// printf( "consumer %d ,%#x . \n" , thrd_id ,arg);
这两句话就可以知道,当传递的是thrd_num地址时候,由于进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。地址, 由于进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。 thrd_num的值会随着线程的执行而发生改变,系统调度频率之快是我们无法想像的,所以thrd_num的值也是动态改变的。
三、条件变量(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信号, 表示缓冲区有位置可以生产了, 你快生产吧。然后解锁互斥量。就这样,生产者消费者和谐同步工作着。
流程图我就不画了,看代码也能明白过程:
---producer过程:lock(mutex)->checknotfull->(if notfull wait until notfull)->produce product->sendnotempty to consumer->unlock(mutex)
---consumer过程:lock(mutex)->checknotempty->(if notempty wait until notempty)->get productfrom buffer->send notfull to poducer->unlock(mutex)
- #include <stdio.h>
- #include <pthread.h>
-
- #define LOOP_COUNT 20 //生产者和消费者各自循环次数,也可以说生产商品的总量
- //#define LOOP_THRD 5 //消费者线程个数
- #define BUFSIZE 5 //缓冲区大小,也就是最多能放多少个产品
-
- pthread_mutex_t mutex; //定义一个全局互斥量,在不同函数中
- //初始化和使用
- pthread_cond_t notempty , notfull; //定义两个条件变量,当作信号投放
- unsigned int prod_pos = 3; //定义生产者在缓冲区开始生产的位置
- unsigned int cons_pos = 0; //定义消费者在缓冲区开始消费的位置
-
- 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 ){
- printf( "producer try to lock .\n");
- pthread_mutex_lock( &mutex ); //加锁
-
- /*
- 成功占有互斥量,接着检查缓冲区是不是满了,
- */
- if( ( prod_pos + 1 ) % BUFSIZE == cons_pos ){
- //缓冲区满了
- printf( "producer wait not full.\n");
- pthread_cond_wait( ¬full , &mutex ); //等待条件满足
- }
- //如果没满,接下来可以对缓冲区(仓库)进行生产
- /*
- 成功占有互斥量,接着检查缓冲区是不是满了,
- */
- if( ( prod_pos + 1 ) % BUFSIZE == cons_pos ){
- //缓冲区满了
- printf( "producer wait not full.\n");
- pthread_cond_wait( ¬full , &mutex ); //等待条件满足
- }
- //如果没满,接下来可以对缓冲区(仓库)进行生产
- //操作
- printf( "producer lock successful, producer put %d's "
- "product to buffer.\n" ,count);
- prod_pos = ( prod_pos +1 ) % BUFSIZE; &n