7.2互斥锁和条件变量
value的值可以是PTHREAD_PROCESS_PRIVATE或者PTHREAD_PROCESS_SHARED,后者被称作进程间共享属性
互斥锁知道相互排斥,他是最基本的同步形式。互斥锁用于保护临界区,以保证任何时刻只有一个线程(进程)在执行其中的代码。
互斥锁被声明为具有pthread_mutex_t类型的变量。如果互斥锁是静态分配的,那么我们可以把它初始化成常值PTHREAD_INITIALIZER
static pthread_mutex_t lock=PTHREAD_INITIALIZER
如果互斥量是动态分配的那么我们调用pthread_mutex_init函数来初始化它
下面三个函数来给互斥锁上锁和解锁
#include<pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mptr);
int pthread_mutex_trylock(pthread_mutex_t *mptr);
int pthread_mutex_unlock(pthread_mutex_t *mptr);
//若成功返回0,若出错则为正的Exx值
如果尝试给一个已经被另外线程锁住的互斥锁上锁,那么pthread_mutex_lock将会阻塞到该互斥锁解锁为止。pthread_mutex_trylock是对应的非阻塞函数,如果该互斥锁已经锁住,那么它就返回一个EBUSY错误
尽管我们说互斥锁保护的是临界区,实际上保护的是在临界区被操纵的数据。也就是说互斥锁通常保护被多个进程或线程分享的共享数据
7.3生产者-消费者问题
在单个进程中有多个生产者和一个消费者。整数数组有buff含有被生产和消费条目
第一个例子中,我们只关心生产者线程之间的同步,知道所有生产者线程都完成工作之后才开始消费者线程
//只考虑生产者之间的同步
#include<unpipc>
#define MAXNITEMS 1000000
#define MAXNTHREADS 100
int nitems;
struct
{
pthread_mutex_t mutex;
int nval;
int nbuf;
int buff[MAXNITEMS];
}shared={PTHREAD_MUTEX_INITIALIZER};
void *produce(void *),*consume(void *)
int main(int argc,char**argv)
{
int i,nthread,count[MAXITEMS];
pthread_t tid_produce[MAXTHREADS],tid_consume;
nthreads=min(atoi(argv[2]),MAXNTHREADS);
nitems=min(atoi(argv[1],MAXNITEMS));
set_concurrency(nthreads);
for(i=0;i<nthreads;i++)
{
count[i]=0;
pthread_create(&tid_produce[i],NULL,produce,&count[i]);
}
for(i=0;i<nthread;i++)
{
pthread_join(tid_produce[i],NULL);
printf("count[%d]=%d\n",i,count[i]);
}
pthread_create(&tid_consume,NULL,consume,NULL);
pthread_join(tid_consume,NULL);
exit(0);
}
void * produce(void *arg)
{
for(;;)
{
pthread_mutex_lock(&shared.mutex);
if(shared.nput>=shared.nitems)
{
pthread_mutex_unlock(&shared.mutex);
return (NULL);
}
shared.buff[shared.nput]=shared.nval;
shared.nput++;
shared.nval++;
pthread_mutex_unlock(&shared.mutex);
*((int *)arg)+=1;
}
}
//考虑消费者与生产者之间的同步
void *consume(void *arg)
{
int i;
for(i=0;i<nitems;i++)
{
consume_wait(i);
if(shared.buff[i]!=i)
printf("错误");
}
ruturn (NULL);
}
void consume_wait(int i)
{
for(;;)
{
pthread_mutex_lock(&shared.mutex); //一次又一次的循环,每次互斥锁解锁又上锁,这称作轮询,是一种对cpu时间的浪费
if(i<shared.nput)
{
pthread_mutex_unlock(&shared.mutex);
return;
}
pthread_mutex_lock(&shared.mutex);
}
}
7.5条件变量:等待与信号发送
互斥锁用于上锁,条件变量用于等待,这两种不同类型的同步都是需要的
条件变量是类型为pthread_cond_t的变量,以下两个函数用到这个变量
#include<pthread.h>
int pthread_cond_wait(pthread_cond_t *cptr,pthread_mutex_t *mptr)
int pthread_cond_sigal(pthread_cond_t *cptr);
每个条件变量都会指定一个互斥锁与其关联。我们调用pthread_cond_wait等待某个条件为真时,还会指定其条件变量的地址和与之关联的互斥锁的地址
#include<unipc.h>
int nitems;
int buff[MAXNITEMS];
struct{
pthread_mutex_t mutex;
int nput;
int nval;
}shared={PTHREAD_MUTEX_INITIALIZER};
struct
{
pthread_cond_t cond;
pthread_mutex_t mutex;
int nready;
}put={PTHREAD_COND_INITIALIZER,PTHREAD_MUTEX_INITIALIZER};
void *produce(void *arg)
{
for(;;)
{
pthread_mutex_lock(&shared.mutex);
if(nput>=nitems)
{
pthread_mutex_unlock(&shared.mutex);
rethrn (NULL);
}
buff[nput]=shared.nval;
shared.nput++;
shared.nval++;
pthread_mutex_lock(&shared.mutex);
pthread_mutex_lock(&put.mutex);
if(nready==0)
pthread_cond_sigal(&put.cond);
nready++;
pthread_mutex_unlock(&put.mutex);
*((int*)arg)+=1;
}
}
void *consume(void *arg)
{
int i;
for(i=0;i<nitems;i++)
{
pthread_mutex_lock(&put.mutex);
while(nready==0)
pthread_cond_wait(&put.mutex,&put.cond);
nready--;
pthread_mutex_unlock(&put.mutex);
}
return (NULL);
}
pthread_cond_wait函数原子地执行两个操作
1.给互斥锁put.mutex解锁
2.把调用线程投入睡眠,直到另外某个线程就本条件变量调用pthread_cond_sigal
struct
{
pthread_mutex_t mutex;
phtread_cond_t cond;
维护本条件的各个变量;
}var={PTHREAD_MUTEX_INITIALIZER,PHTREAD_COND_INITIALIZER};
pthread_mutex_lock(&var.mutex);
设置条件为真;
pthread_cond_sigal(&var.cond);
pthread_mutex_unlock(&var.mutex);
pthread_mutex_lock(&put.mutex);
while(条件为假)
pthread_cond_wait(&put.cond);
prhread_mutex_unlock(&put.mutex);
避免上锁
冲突
在上面的代码段中,pthread_cond_sigal由当前锁住某个互斥锁的线程调用,而该互斥锁与本函数将给他发送信号的条件变量关联的。我们可以设想一下最坏情况:当该条件变量被发送信号后,系统立即调用等待在其上的线程,该线程开始运行,但立即停止,因为他没能获取相应的互斥锁,为避免上锁冲突代码可做如下变动
int dosigal;
pthread_mutex_lock(&put.mutex);
dosigal=(put.nready==0);
put.nready++;
pthread_mutex_unlock(&put.mutex);
if(dosigal)
phtread_cond_sigal(&put.cond)
这里直到释放与之关联的互斥锁之后才会向条件变量发送信号,是允许这么做的:调用pthread_cond_sigal的线程不必是与之关联的互斥量的当前属主
7.6条件变量:定时等待和广播
通常pthread_cond_sigal只能唤醒等待在相应条件变量上的一个线程。在某些条件下,一个线程认定有多个其他线程应被唤醒,这是他可以调用pthread_cond_broadcast唤醒阻塞在相应条件变量上的所有线程
#inlcude<pthread.h>
int phtread_cond_broadcast(pthread_cond_t *cptr)
int pthread_cond_timedwait(pthread_cond_t *cptr,pthread_mutex_t *mptr,const struct timespec *abstime)
即便当时相应的条件变量还没有收到信号,如果发生这种超时情况,该函数就返回ETIMEDOUT
7.7互斥锁和条件变量的属性
以上使用的条件变量和互斥锁把它们作为一个进程中的全局变量存放,他们用于进程内各个线程的同步,我们用两个常值PTHREAD_MUTEX_INTTIALIZER和PTHREAD_COND_INITIALIZER来初始化它们;由这种方式初始化的互斥锁和条件变量都具备默认属性。
条件变量和互斥锁是用以下函数初始化或摧毁的
#include<pthread.h>
int pthread_cond_init(pthread_cond_t *cptr,const pthread_condattr_t *attr)
int pthread_cond_destroy(pthread_cond_t *cptr)
int pthread_mutex_init(pthread_mutex_t *mptr,const pthread_mutexattr_t *attr)
int pthread_mutex_destroy(pthread_mutex_t *mptr)
互斥锁属性的数据类型是pthread_mutexattr_t,条件变量属性的数据类型是pthread_condattr_t
它们由以下函数初始化或摧毁
int pthread_mutexattr_init(pthread_mutexattr_t *attr)
int pthread_mutexattr_destory(pthread_mutexattr *attr)
int pthread_condattr_init(pthread_condattr_t *attr)
int pthread_condattr_destory(pthread_condattr_t *attr)
指定互斥锁和条件变量在不同进程间共享而不只是在单个进程内的不同线程间共享,这个属性是用
以下函数获得的
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *attr,int * valptr)
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr,int valptr)
int pthread_condattr_getpshared(const pthread_condattr_t *attr,int * valptr)
int pthread_condattr_setpshared(pthread_condattr_t *attr,int * valptr)
value的值可以是PTHREAD_PROCESS_PRIVATE或者PTHREAD_PROCESS_SHARED,后者被称作进程间共享属性