线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步(下文统称为同步)。
生产者消费者问题就是一个著名的线程同步问题,该问题描述如下:有一个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个具有多个缓冲区的缓冲池,生产者将它生产的产品放入一个缓冲区中,消费者可以从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个已经放入产品的缓冲区中再次投放产品。
关于线程同步和互斥的详细说明可以看:http://blog.csdn.net/big_bit/article/details/51356381这篇文章
用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。内核模式下的方法有:事件,信号量,互斥量。下面我们来分别看一下这些方法:
一、互斥锁或互斥量(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); //休眠一秒,防止它又马上占据锁 } }
#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); //休眠一秒, 防止马上又占据读锁 } }
与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。条件变量分为两部分: 条件和变量。条件本身是由互斥量保护的。线程在改变条件状态前先要锁住互斥量。条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。
#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 ); //等待条件满足 } //如果没满,接下来可以对缓冲区(仓库)进行生产 //操作 printf( "producer lock successful, producer put %d's " "product to buffer.\n" ,count); prod_pos = ( prod_pos +1 ) % BUFSIZE; //下标前进一个 pthread_cond_signal( ¬empty ); //向消费者发送信号 /* 休眠3秒, 便于程序观察,可以看到 其他读取线程不能占据锁而阻塞 */ sleep( 1 ); printf("prducer finished ,unlock lock.\n"); pthread_mutex_unlock( &mutex ); //解锁 sleep( 1 ); //休眠一秒, 防止马上又占据写锁 } } void *consumer( void *arg ){ int count = 0 ; //循环计数 while( count++ < LOOP_COUNT ){ // sleep( thrd_id+1 ); //休眠一秒, 便于程序观察 printf( "consumer try to lock .\n" ); pthread_mutex_lock( &mutex ); //解锁 /* 成功占有互斥量,接下来检查缓冲区是否为空 */ if( cons_pos == prod_pos ){ printf( "consumer wait not empty.\n"); pthread_cond_wait( ¬empty , &mutex ); } //缓冲区不空,可以对缓冲区(仓库)进行取出操作 printf( " consumer locked successful ,consumer " "get %d product from buffer.\n" , count); cons_pos = ( cons_pos + 1) % BUFSIZE ; //下标前进一个 pthread_cond_signal( ¬full ); //向生产着发送信号 /* 休眠3秒, 便于程序观察,可以看到 其他读取线程能占据读锁 */ sleep( 1 ); printf("consumer finished ,unlock lock.\n"); pthread_mutex_unlock( &mutex ); //解锁 sleep(1); //休眠一秒, 防止马上又占据读锁 } }
函数将解锁mutex参数指向的互斥锁,并使当前线程阻塞在cond参数指向的条件变量上。
被阻塞的线程可以被pthread_cond_signal函数,pthread_cond_broadcast函数唤醒,也可能在被信号中断后被唤醒。pthread_cond_wait函数的返回并不意味着条件的值一定发生了变化,必须重新检查条件的值。
pthread_cond_wait函数返回时,相应的互斥锁将被当前线程锁定,即使是函数出错返回。
一般一个条件表达式都是在一个互斥锁的保护下被检查。当条件表达式未被满足时,线程将仍然阻塞在这个条件变量上。当另一个线程改变了条件的值并向条件变量发出信号时,等待在这个条件变量上的一个线程或所有线程被唤醒,接着都试图再次占有相应的互斥锁。
阻塞在条件变量上的线程被唤醒以后,直到pthread_cond_wait()函数返回之前条件的值都有可能发生变化。所以函数返回以后,在锁定相应的互斥锁之前,必须重新测试条件值。最好的测试方法是循环调用pthread_cond_wait函数,并把满足条件的表达式置为循环的终止条件。
所以上述代码应该用循环而不是if。具体修改如下:consumer函数中: /* 成功占有互斥量,接下来循环检查缓冲区是否为空. 这个while要特别 说明一下,单个pthread_cond_wait功能很完善,为何这里要有一个 while (cons_pos >=prod_pos)呢?因为pthread_cond_wait里的线程可 能会被意外唤醒返回了,mutex又被重新lock(不一定是本线程,有可能 是其他线程),此时情况是cons_pos >= prod_pos ,表示缓冲区空了, 不能再取product,也没有product可取。这不是我们想要的结果。应该 让线程继续进入pthread_cond_wait */ while( cons_pos == prod_pos ){ printf( "consumer wait not empty.\n"); /* pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的 mutex,然后阻塞在等待对列里休眠,直到再次被唤醒(大多数 情况下是等待的条件成立而被唤醒,唤醒后,该进程会先锁定先 pthread_mutex_lock(&mutex);,再读取资源,用这个流程是比较 清楚的 block-->unlock-->cond_wait() return-->lock */ pthread_cond_wait( ¬empty , &mutex ); }
produer函数中: /* 成功占有互斥量,接着循环检查缓冲区是不是满了, */ while( ( prod_pos + 1 ) % BUFSIZE == cons_pos ){ //缓冲区满了 printf( "producer wait not full.\n"); pthread_cond_wait( ¬full , &mutex ); //等待条件满足 }
while( ( prod_pos + 1 ) % BUFSIZE == cons_pos )
while( cons_pos == prod_pos )