在线程和进程的实现中,完成数据共享需要同步的,互斥锁和条件变量是很重要的组成部分。
1.互斥锁
互斥锁是用来保护临界区资源,实际上保护的是临界区中被操纵的数据,互斥锁通常用于保护由多个线程或多进程分享的共享数据。一般是一些可供线程间使用的全局变量,来达到线程同步的目的
,
即保证任何时刻只有一个线程或进程在执行其中的代码。
posix的互斥锁被声明为pthread_mutex_t类型的变量。互斥锁也分为静态互斥锁和动态互斥锁两类,如果是静态互斥锁,常常会把它初始化为一个常值PTHREAD_MUTEX_INITIALIZER,就像这样:
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
如果互斥锁是动态分配的,比如通过malloc来分配的或者是分配在共享内存中,那么真正使用它之前一定要用pthread_mutex_init函数来对它做初始化。下面的三个函数是对互斥锁进行上锁和解锁:
#include
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,失败返回相应的errno值。
看一个例子:功能是:主线程初始化一个互斥锁和上锁,然后创建子线程,子线程上锁,等待主线程执行完毕后,解锁,子线程开始执行,最后子线程解锁,销毁锁。
#include<stdio.h>
#include<pthread.h>
#include <unistd.h>
static pthread_mutex_t testlock;
pthread_t test_thread;
void *test(void *)
{
pthread_mutex_lock(&testlock);
printf("thread Test() \n");
pthread_mutex_unlock(&testlock);
}
int main()
{
pthread_mutex_init(&testlock, NULL);
pthread_mutex_lock(&testlock);
printf("Main lock \n");
pthread_create(&test_thread, NULL, &test, NULL);
sleep(1); //更加明显的观察到是否执行了创建线程的互斥锁
printf("Main unlock \n");
pthread_mutex_unlock(&testlock);
sleep(1);
pthread_join(test_thread,NULL);
pthread_mutex_destroy(&testlock);
return 0;
}
编译的时候: g++ -o test unix_test_messge.cpp -lrt -lpthread(加上链接库)
生产者和消费者问题:(多个生产者一个消费者)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#define MAXNITEMS 1000000
#define MAXNTHREADS 100
int nitems;
struct s1
{
pthread_mutex_t mutex;
int buff[MAXNITEMS];
int nput;
int nval;
} shared = {
PTHREAD_MUTEX_INITIALIZER
};
void *produce(void*);
void *consume(void*);
int main(int argc,char *argv[])
{
int i,nthreads,count[MAXNTHREADS];
pthread_t tid_produce[MAXNTHREADS],tid_consume;
if(argc != 3)
{
printf("usage: producongs2 <#itmes> <#threads>.\n");
exit(0);
}
nitems = atoi(argv[1]);
nthreads = atoi(argv[2]);
pthread_setconcurrency(nthreads); //设置线程并发级别
for(i=0;i<nthreads;++i)
{
count[i] = 0;
pthread_create(&tid_produce[i],NULL,produce,&count[i]);
pthread_join(tid_consume,NULL); //等待线程退出
exit(0);
}
void *produce(void *arg)
{
for(; ;)
{
pthread_mutex_lock(&shared.mutex); //加锁
if(shared.nput >= 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;
}
// return ;
}
void *consume(void *arg)
{
int i;
printf("consume \n");
printf("%d",nitems);
printf("\n");
for(i=0;i<nitems;i++)
{
if(shared.buff[i] != i)
printf("buff[%d] = %d\n",i,shared.buff[i]);
}
return NULL;
}
2.条件变量:
需要的头文件:pthread.h条件变量标识符:pthread_cond_t
1、互斥锁的存在问题:
互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。设想一种简单情景:多个线程访问同一个共享资源时,并不知道何时应该使用共享资源,如果在临界区里加入判断语句,或者可以有效,但一来效率不高,二来复杂环境下就难以编写了,这是我们需要一个结构,能在条件成立时触发相应线程,进行变量修改和访问。
2、条件变量:
条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。
3、条件变量的相关函数
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //条件变量结构
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t*cond_attr);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
const struct timespec *abstime);
int pthread_cond_destroy(pthread_cond_t *cond);
详细说明:
(1)创建和注销
条件变量和互斥锁一样,都有静态动态两种创建方式
a.静态方式
静态方式使用PTHREAD_COND_INITIALIZER常量,如下:
pthread_cond_t cond=PTHREAD_COND_INITIALIZER
b.动态方式
动态方式调用pthread_cond_init()函数,API定义如下:
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr)
尽管POSIX标准中为条件变量定义了属性,但在LinuxThreads中没有实现,因此cond_attr值通常为NULL,且被忽略。
注销一个条件变量需要调用pthread_cond_destroy(),只有在没有线程在该条件变量上等待的时候才能注销这个条件变量,否则返回EBUSY。因为Linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程。API定义如下:int pthread_cond_destroy(pthread_cond_t *cond)
(2)等待和激发
a.等待
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) //等待
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
const struct timespec *abstime) //有时等待
互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。设想一种简单情景:多个线程访问同一个共享资源时,并不知道何时应该使用共享资源,如果在临界区里加入判断语句,或者可以有效,但一来效率不高,二来复杂环境下就难以编写了,这是我们需要一个结构,能在条件成立时触发相应线程,进行变量修改和访问。
2、条件变量:
条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。
3、条件变量的相关函数
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //条件变量结构
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t*cond_attr);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
const struct timespec *abstime);
int pthread_cond_destroy(pthread_cond_t *cond);
详细说明:
(1)创建和注销
条件变量和互斥锁一样,都有静态动态两种创建方式
a.静态方式
静态方式使用PTHREAD_COND_INITIALIZER常量,如下:
pthread_cond_t cond=PTHREAD_COND_INITIALIZER
b.动态方式
动态方式调用pthread_cond_init()函数,API定义如下:
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr)
尽管POSIX标准中为条件变量定义了属性,但在LinuxThreads中没有实现,因此cond_attr值通常为NULL,且被忽略。
注销一个条件变量需要调用pthread_cond_destroy(),只有在没有线程在该条件变量上等待的时候才能注销这个条件变量,否则返回EBUSY。因为Linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程。API定义如下:int pthread_cond_destroy(pthread_cond_t *cond)
(2)等待和激发
a.等待
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) //等待
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
const struct timespec *abstime) //有时等待
等待条件有两种方式:无条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),其中计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待,其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。
无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或 pthread_cond_timedwait(),下同)的竞争条件(Race Condition)。mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开 pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。
b.激发
激发条件有两种形式,pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而pthread_cond_broadcast()则激活所有等待线程。
(3)其他操作
pthread_cond_wait ()和pthread_cond_timedwait()都被实现为取消点,因此,在该处等待的线程将立即重新运行,在重新锁定mutex后离开 pthread_cond_wait(),然后执行取消动作。也就是说如果pthread_cond_wait()被取消,mutex是保持锁定状态的,因而需要定义退出回调函数来为其解锁。
pthread_cond_wait实际上可以看作是以下几个动作的合体:
解锁线程锁;
等待条件为true;
加锁线程锁;
无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或 pthread_cond_timedwait(),下同)的竞争条件(Race Condition)。mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开 pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。
b.激发
激发条件有两种形式,pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而pthread_cond_broadcast()则激活所有等待线程。
(3)其他操作
pthread_cond_wait ()和pthread_cond_timedwait()都被实现为取消点,因此,在该处等待的线程将立即重新运行,在重新锁定mutex后离开 pthread_cond_wait(),然后执行取消动作。也就是说如果pthread_cond_wait()被取消,mutex是保持锁定状态的,因而需要定义退出回调函数来为其解锁。
pthread_cond_wait实际上可以看作是以下几个动作的合体:
解锁线程锁;
等待条件为true;
加锁线程锁;
看下面一个程序:(条件变量实现的生产者和消费者)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#define MAXNITEMS 1000000
#define MAXNTHREADS 100
int nitems;
struct
{
pthread_mutex_t mutex;
int buff[MAXNITEMS];
int nput;
int nval;
} shared = {
PTHREAD_MUTEX_INITIALIZER
};
//条件变量
struct {
pthread_mutex_t mutex;
pthread_cond_t cond;
int nready;
}nready = {
PTHREAD_MUTEX_INITIALIZER,PTHREAD_COND_INITIALIZER
};
void *produce(void*);
void *consume(void*);
int main(int argc,char *argv[])
{
int i,nthreads,count[MAXNTHREADS];
pthread_t tid_produce[MAXNTHREADS],tid_consume;
if(argc != 3)
{
printf("usage: producongs2 <#itmes> <#threads>.\n");
exit(0);
}
nitems = atoi(argv[1]);
nthreads = atoi(argv[2]);
pthread_setconcurrency(nthreads+1);
for(i=0;i<nthreads;++i)
{
count[i] = 0;
pthread_create(&tid_produce[i],NULL,produce,&count[i]);
}
pthread_create(&tid_consume,NULL,consume,NULL);
for(i=0;i<nthreads;i++)
{
pthread_join(tid_produce[i],NULL);
printf("count[%d] = %d\n",i,count[i]);
}
pthread_join(tid_consume,NULL);
exit(0);
}
void *produce(void *arg)
{
printf("producer begins work\n");
for(; ;)
{
pthread_mutex_lock(&shared.mutex);
if(shared.nput >= nitems)
{
pthread_mutex_unlock(&shared.mutex);
return ;
}
shared.buff[shared.nput] = shared.nval;
shared.nput++;
shared.nval++;
pthread_mutex_unlock(&shared.mutex);
pthread_mutex_lock(&nready.mutex);
if(nready.nready == 0)
pthread_cond_signal(&nready.cond); //通知消费者
nready.nready++;
pthread_mutex_unlock(&nready.mutex);
*((int*) arg) += 1;
}
}
void *consume(void *arg)
{
int i;
printf("consuemer begins work.\n");
for(i=0;i<nitems;i++)
{
pthread_mutex_lock(&nready.mutex);
while(nready.nready == 0)
pthread_cond_wait(&nready.cond,&nready.mutex); //等待生产者
nready.nready--;
pthread_mutex_unlock(&nready.mutex);
if(shared.buff[i] != i)
printf("buff[%d] = %d\n",i,shared.buff[i]);
}
return;
}
代码引用博客:http://www.cnblogs.com/Anker/archive/2013/01/09/2852442.html