IPC通信:互斥锁和条件变量
互斥锁机制(Mutual exclusion,缩写为Mutex)是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。该目的通过将代码切片成一个一个的临界区域(critical section)达成。临界区域指的是一块对公共资源进行存取的代码,并非一种机制或是算法
初始化: 在Linux下, 线程的互斥量数据类型是pthread_mutex_t. 在使用前, 要对它进行初始化:对于静态分配的互斥量, 可以把它设置为PTHREAD_MUTEX_INITIALIZER, 或者调用pthread_mutex_init.对于动态分配的互斥量, 在申请内存(malloc)之后, 通过pthread_mutex_init进行初始化, 并且在释放内存(free)前需要调用pthread_mutex_destroy.
原型:int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restric attr);
返回值: 成功则返回0, 出错则返回错误编号.
说明: 如果使用默认的属性初始化互斥量,只需把attr设为NULL
互斥操作: 对共享资源的访问, 要对互斥量进行加锁, 如果互斥量已经上了锁, 调用线程会阻塞, 直到互斥量被解锁. 在完成了对共享资源的访问后, 要对互斥量进行解锁
加锁和解锁函数原型:
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值: 成功则返回0, 出错则返回错误编号.
说明: 具体说一下trylock函数, 这个函数是非阻塞调用模式, 也就是说, 如果互斥量没被锁住, trylock函数将把互斥量加锁, 并获得对共享资源的访问权限; 如果互斥量被锁住了, trylock函数将不会阻塞等待而直接返回EBUSY, 表示共享资源处于忙状态
测试实例进一步了解互斥
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int lock_var;
time_t end_time;
void pthread1(void *arg);
void pthread2(void *arg);
int main(int argc, char *argv[])
{
pthread_t id1,id2;
pthread_t mon_th_id;
int ret;
end_time = time(NULL)+10;
/*互斥锁初始化*/
pthread_mutex_init(&mutex,NULL);
/*创建两个线程*/
ret=pthread_create(&id1,NULL,(void *)pthread1, NULL);
if(ret!=0)
perror("pthread cread1");
ret=pthread_create(&id2,NULL,(void *)pthread2, NULL);
if(ret!=0)
perror("pthread cread2");
pthread_join(id1,NULL);
pthread_join(id2,NULL);
exit(0);
}
void pthread1(void *arg)
{
int i;
while(time(NULL) < end_time){
/*互斥锁上锁*/
if(pthread_mutex_lock(&mutex)!=0){
perror("pthread_mutex_lock");
}
else
printf("pthread1:pthread1 lock the variable\n");
for(i=0;i<2;i++){
sleep(1);
lock_var++;
}
/*互斥锁接锁*/
if(pthread_mutex_unlock(&mutex)!=0){
perror("pthread_mutex_unlock");
}
else
printf("pthread1:pthread1 unlock the variable\n");
sleep(1);
}
}
void pthread2(void *arg)
{
int nolock=0;
int ret;
while(time(NULL) < end_time){
/*测试互斥锁*/
ret=pthread_mutex_trylock(&mutex);
if(ret==EBUSY)
printf("pthread2:the variable is locked by pthread1\n");
else{
if(ret!=0){
perror("pthread_mutex_trylock");
exit(1);
}
else
printf("pthread2:pthread2 got lock.The variable is %d\n",lock_var);
/*互斥锁接锁*/
if(pthread_mutex_unlock(&mutex)!=0){
perror("pthread_mutex_unlock");
}
else
printf("pthread2:pthread2 unlock the variable\n");
}
sleep(3);
}
}
zhaoxj$ ./thread_mutex
pthread1:pthread1 lock the variable
pthread2:the variable is locked by pthread1
pthread1:pthread1 unlock the variable
pthread2:pthread2 got lock.The variable is 2
pthread2:pthread2 unlock the variable
pthread1:pthread1 lock the variable
pthread1:pthread1 unlock the variable
pthread2:pthread2 got lock.The variable is 4
pthread2:pthread2 unlock the variable
pthread1:pthread1 lock the variable
pthread1:pthread1 unlock the variable
pthread2:pthread2 got lock.The variable is 6
pthread2:pthread2 unlock the variable
pthread1:pthread1 lock the variable
pthread1:pthread1 unlock the variable
(实现一段代码原子操作)说明:mutex互斥信号量锁住的不是一个变量,而是阻塞住一段程序。如果对一个mutex变量执行了第一次pthread_mutex_lock(mutex)之后,在unlock(mutex)之前的这段时间内,如果有其他线程也执行到了pthread_mutex_lock(mutex),这个线程就会阻塞住,直到之前的线程unlock之后才能执行,由此,实现同步,也就达到保护临界区资源的目的
条件变量 条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。
创建和注销
1)条件变量和互斥锁一样,都有静态动态两种创建方式,静态方式使用PTHREAD_COND_INITIALIZER常量:pthread_cond_t cond=PTHREAD_COND_INITIALIZER;动态方式调用pthread_cond_init()函数:int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr)
2)注销一个条件变量需要调用pthread_cond_destroy(),只有在没有线程在该条件变量上等待的时候才能注销这个条件变量,否则返回EBUSY。因为Linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程。int pthread_cond_destroy(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)
等待条件有两种方式:无条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),其中计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待,其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。
无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的竞争条件(Race Condition)。在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在线程挂起进入等待时mutex将被解锁;在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。可概括为:调用pthread_cond_wait()前线程要显示的加锁,pthread_cond_wait()结束后线程要显示的解锁。pthread_cond_wait()被调用时对本线程的互斥量进行行隐式解锁和加锁过程。
激发条件有两种形式:pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而pthread_cond_broadcast()则激活所有等待线程。 两者 如果没有等待的线程,则什么也不做。当pthread_cond_t调用pthread_cond_wait进入等待状态时,pthread_mutex_t互斥信号无效. 示例代码
1 #include <stdio.h>
2 #include<pthread.h> //多线程所用头文件
3 #include <semaphore.h> //信号量使用头文件
4
5 pthread_cond_t g_cond ; //申明条锁,并用宏进行初始化
6 pthread_mutex_t g_mutex ;
7
8 void threadFun1(void)
9 {
10 int i;
11 pthread_mutex_lock(&g_mutex); //1
12 pthread_cond_wait(&g_cond,&g_mutex); //如g_cond无信号,则阻塞
13
14 for( i = 0;i < 2; i++ ){
15 printf("thread threadFun1.\n");
16 sleep(1);
17 }
18
19 pthread_cond_signal(&g_cond);
20 pthread_mutex_unlock(&g_mutex);
21 }
22
23 int main(void)
24 {
25 pthread_t id1; //线程的标识符
26 pthread_t id2;
27
28 pthread_cond_init(&g_cond,NULL); //也可以程序里面初始化
29 pthread_mutex_init(&g_mutex,NULL); //互斥变量初始化
30
31 int i,ret;
32 ret = pthread_create(&id1,NULL,(void *)threadFun1, NULL);
33
34 if ( ret!=0 ) { //不为0说明线程创建失败
35 printf ("Create pthread1 error!\n");
36 exit (1);
37 }
38
39 sleep(5); //等待子线程先开始
40 pthread_mutex_lock(&g_mutex); //2
41 pthread_cond_signal(&g_cond); //给个开始信号,注意这里要先等子线程进入等待状态在发信号,否则无效
42 sleep(5); //此刻子线程仍在等待,即子线程未被调度
43 pthread_mutex_unlock(&g_mutex);//解锁之后 threadfun1 才开始执行
44
45 pthread_join(id1,NULL);
46 pthread_cond_destroy(&g_cond); //释放
47 pthread_mutex_destroy(&g_mutex); //释放
48
49 return 0;
50 }
请看红颜色的1和2 ,明明是1先锁了互斥变量,但代码执行到2还是一样可以锁定.为什么会这样呢?
问题解释:当程序进入pthread_cond_wait等待后,将会把g_mutex进行解锁,当离开pthread_cond_wait之前,g_mutex会重新加锁。所以在main中的g_mutex会被加锁。