线程间的同步方法大体可分为两类:用户模式和内核模式。顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态,而用户模式就是不需要切换到内核态,只在用户态完成操作。
用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。
内核模式下的方法有:事件,信号量,互斥量。
下面我们来分别看一下这些方法:
一、互斥锁或互斥量(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 4
wm_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;
}