Linux线程浅析[线程的同步和互斥之线程同步的条件变量]
- 线程同步的条件变量
- 经典的写者和读者的同步问题
线程同步的条件变量
线程同步—–条件变量
互斥锁的特点就是它只有两种状态:锁定和非锁定
条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足
条件变量的内部机制:
条件变量内部是一个等待队列。放置等待的线程,线程在条件变量上等待和通知,互斥锁用来保护等待队列(对等待队列进行上锁),条件变量通常和互斥锁一起使用,条件变量允许线程等待特定条件的发生,当条件不满足时,线程通常先进入阻塞状态,等待条件发生,一旦其他的某个线程改变了条件,可唤醒一个或多个阻塞线程
具体的判断条件还需用户给出(进行同步通知之前必须要确保其处于一种等待状态,同时也需要注意防止死锁的发生),如监听其是否处于一种等待状态等
条件变量类型:pthread_cond_t
条件变量的创建和销毁:
#include<pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,pthread_condattr *restrict attr);
int pthread_cond_init(pthread_cond_t *cond);
返回:成功返回0,失败返回错误编码
参数:
cond:条件变量
attr:条件变量属性
条件变量的等待
#include<pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex)
int pthread_cond_timewait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct * restrict timeout)
返回:成功返回0,失败返回错误编号
struct timespec{
time_t tv_sec;
long tv_nsec;
}
参数:
cond:条件变量
mutex:互斥锁
互斥锁是对条件变量cond的保护
线程由于调用wait函数阻塞,否则释放互斥锁
条件变量的唤醒
条件变量通知操作
#include<pthread.h>
int pthread_cond_signal(ptrhead_cond_t *cond) //唤醒指定的线程
int pthraed_cond_broadcast(pthread_cond_t *cond)//唤醒所有等待的线程
返回:成功返回0,失败返回错误编号
参数:
cond:条件变量
当条件满足时,线程需要通知等待的线程
pthread_cond_signal函数通知单个线程
pthread_cond_broadcast函数同志所有线程
浅析pthread_cond_wait()函数内部执行的相关流程(伪代码块):
pthread_cond_wait()函数内部流程{
1:unlock(&mutex);//释放锁
2:lock(&mutex);
3:将线程自己插入到条件变量的等待队列中去
4:unlock(&mutex);
5:当前等待的线程就阻塞了 <========等待其他线程通知唤醒,broadcast/signal
6:在唤醒后调用 lock(&mutex);
7:从等待队列中去删除等待队列自己
}
注意:从上面可以得到:每一次在调用完wait函数之后,其实都是需要有一个释放锁的过程.
先上一个简单的案例,线程通知一对一的,一个线程负责进行计算.,另外一个线程负责去获取计算后的结果
/*
* ===========================================================================
*
* Filename: pthread_cond_cal.c
* Description:
* Version: 1.0
* Created: 2017年04月03日 10时38分37秒
* Revision: none
* Compiler: gcc
* Author: (),
* Company:
*
* ===========================================================================
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
typedef struct{
int res;
//定义一个判断条件,当为0的时候,即非阻塞等待状态,为1的时候为阻塞等待状态
int is_wait;
//定义一个互斥锁
pthread_mutex_t cal_mutex_t;
//定义一个条件变量
pthread_cond_t cal_cond_t;
}CalType;
//定义一个处理计算的函数
void* cal_th(void* argv){
CalType* calType_p = (CalType*)argv;
int result;
for(result= 0;result <100;result++){
calType_p->res += result;
}
printf("%lx cal result:%d\n",pthread_self(),calType_p->res);
//监听共享资源中的等待状态,当处于0的时候一直阻塞
//注意加锁和释放锁的过程都是对称的
pthread_mutex_lock(&calType_p->cal_mutex_t);
while(!calType_p->is_wait){
pthread_mutex_unlock(&calType_p->cal_mutex_t);
sleep(1);
pthread_mutex_lock(&calType_p->cal_mutex_t);
}
pthread_mutex_unlock(&calType_p->cal_mutex_t);
//通知条件变量停止阻塞状态,执行后面程序,singal函数只是去通知一个线程
pthread_cond_signal(&calType_p->cal_cond_t);
return (void*)0;
}
void* get_th(void* argv){
CalType* calType_p = (CalType*)argv;
//操作共享资源的时候一定要注意加锁,并且需要注意的是防止形成一种死锁状态
pthread_mutex_lock(&calType_p->cal_mutex_t);
calType_p ->is_wait = 1;
//条件变量处于等待阻塞的状态
pthread_cond_wait(&calType_p->cal_cond_t,&calType_p->cal_mutex_t);
int result = calType_p->res;
printf("%lx get result:%d\n",pthread_self(),result);
pthread_mutex_unlock(&calType_p->cal_mutex_t);
return (void*)0;
}
int main(int agrc,char * argv[]){
pthread_t cal_thread,get_thread;
CalType calType;
calType.res = 0;
calType.is_wait = 0;
//初始化互斥锁和条件变量
pthread_mutex_init(&calType.cal_mutex_t,NULL);
pthread_cond_init(&calType.cal_cond_t,NULL);
//创建两个线程
int res ;
if((res = pthread_create(&cal_thread,NULL,cal_th,(void*)&calType))!=0){
perror("cal thread create error");
}
if((res = pthread_create(&get_thread,NULL,get_th,(void*)&calType))!=0){
perror("get thread create error");
}
pthread_join(cal_thread,NULL);
pthread_join(get_thread,NULL);
pthread_mutex_destroy(&calType.cal_mutex_t);
pthread_cond_destroy(&calType.cal_cond_t);
printf("%lx deaded\n",pthread_self());
return 0;
}
从上面可以看出,是去先进行计算线程,然后再去进行获取计算结果的线程,保证了线程执行的顺序性问题.但是上面的案例其实有一个弊端.就是它是一种一对一的形式.只是去通知一个线程,如果去通知多个线程的时候该怎么去通知,怎么设置这样一个判断条件?
对上面的案例进行一种修改的过程,将计算的结果去通知多个线程,多个线程这样就可以分别去做不同的事情了
/*
* ===========================================================================
*
* Filename: pthread_cond_cal.c
* Description:
* Version: 1.0
* Created: 2017年04月03日 10时38分37秒
* Revision: none
* Compiler: gcc
* Author: (),
* Company:
*
* ===========================================================================
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
typedef struct{
int res;
//定义一个判断条件,用来统计阻塞线程的数量
int counter;
//定义一个互斥锁
pthread_mutex_t cal_mutex_t;
//定义一个条件变量
pthread_cond_t cal_cond_t;
}CalType;
//定义一个处理计算的函数
void* cal_th(void* argv){
CalType* calType_p = (CalType*)argv;
int result;
for(result= 0;result <100;result++){
calType_p->res += result;
}
printf("%lx cal result:%d\n",pthread_self(),calType_p->res);
//监听共享资源中的等待状态,当所有线程没有准备好的时候,就需要去等待一下
//注意加锁和释放锁的过程都是对称的
pthread_mutex_lock(&calType_p->cal_mutex_t);
while(calType_p->counter < 2){
pthread_mutex_unlock(&calType_p->cal_mutex_t);
sleep(1);
pthread_mutex_lock(&calType_p->cal_mutex_t);
}
pthread_mutex_unlock(&calType_p->cal_mutex_t);
//通知条件变量停止阻塞状态,执行后面程序,singal函数只是去通知一个线程
pthread_cond_broadcast(&calType_p->cal_cond_t);
return (void*)0;
}
void* get_th(void* argv){
CalType* calType_p = (CalType*)argv;
//操作共享资源的时候一定要注意加锁,并且需要注意的是防止形成一种死锁状态
pthread_mutex_lock(&calType_p->cal_mutex_t);
calType_p ->counter++;
//条件变量处于等待阻塞的状态
pthread_cond_wait(&calType_p->cal_cond_t,&calType_p->cal_mutex_t);
int result = calType_p->res;
printf("%lx get result:%d\n",pthread_self(),result);
pthread_mutex_unlock(&calType_p->cal_mutex_t);
return (void*)0;
}
void *get_th2(void* argv){
CalType* calType_p = (CalType*)argv;
//操作共享资源的时候一定要注意加锁,并且需要注意的是防止形成一种死锁状态
pthread_mutex_lock(&calType_p->cal_mutex_t);
calType_p ->counter++;
//条件变量处于等待阻塞的状态
pthread_cond_wait(&calType_p->cal_cond_t,&calType_p->cal_mutex_t);
int result = calType_p->res;
result = result * 2;
printf("%lx get result:%d\n",pthread_self(),result);
pthread_mutex_unlock(&calType_p->cal_mutex_t);
}
int main(int agrc,char * argv[]){
pthread_t cal_thread,get_thread,get_thread2;
CalType calType;
calType.res = 0;
//初始化线程的数量
calType.counter = 0;
//初始化互斥锁和条件变量
pthread_mutex_init(&calType.cal_mutex_t,NULL);
pthread_cond_init(&calType.cal_cond_t,NULL);
//创建两个线程
int res ;
if((res = pthread_create(&cal_thread,NULL,cal_th,(void*)&calType))!=0){
perror("cal thread create error");
}
if((res = pthread_create(&get_thread,NULL,get_th,(void*)&calType))!=0){
perror("get thread create error");
}
if((res = pthread_create(&get_thread2,NULL,get_th2,(void*)&calType))!=0){
perror("get thread create error");
}
pthread_join(cal_thread,NULL);
pthread_join(get_thread,NULL);
pthread_join(get_thread2,NULL);
pthread_mutex_destroy(&calType.cal_mutex_t);
pthread_cond_destroy(&calType.cal_cond_t);
printf("%lx deaded\n",pthread_self());
return 0;
}
从上面可以看出来,通过线程信号量的形式去将所有的等待线程进行唤醒,线程2将result的数值乘2了
其实在上面的案例中其实都是一种单向的过程,即等待线程是去等待唤醒,然后进行数值操作,但是是不是可以一个线程既可以去等待,友可以去进行唤醒操作呢??这个就是所谓的双向等待和唤醒操作.后面讲的就是双向的经典案例,读者与写者之间的关系
经典的写者和读者的同步问题
浅述下读者与写者之间的关系:
写者负责去写文章.而读者则负责去读取响应文章,当写者写完之后,去通知读者去读取,当读者读完之后,通知写者去继续写.这种关系就是读者与写者之间的关系.还是以代码的形式进行展示吧.
/*
* ===========================================================================
*
* Filename: pthread_reader_writer.c
* Description: 读者与写者的关系,通过线程条件变量去进行阻塞和唤醒
* Version: 1.0
* Created: 2017年04月03日 11时52分35秒
* Revision: none
* Compiler: gcc
* Author: (zzf),
* Company:
*
* ===========================================================================
*/
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<string.h>
typedef struct{
int ins; //内容
//针对写者的状态,条件变量,和互斥锁
int w_is_wait;
pthread_mutex_t w_mutex_t;
pthread_cond_t w_cond_t;
//针对读者的状态,锁,条件变量
int r_is_wait;
pthread_mutex_t r_mutex_t;
pthread_cond_t r_cond_t;
}RWType;
void setIns(RWType * rwTyps,int ins){
rwTyps->ins = ins;
printf("%lx thread setIns:%d\n",pthread_self(),ins);
}
int getIns(RWType* rwTyps){
return rwTyps->ins;
}
/**
* 这个是写者的线程,写者应该是先去进行书写,写完之后然后去读
*/
void *w_th(void* argv){
RWType * rwType_pointer = (void*)argv;
int times = 0;
for(times = 0; times < 80; times++){
//写者将数据写入到共享资源中去
setIns(rwType_pointer,times);
//检查读者是否准备好了,上锁
pthread_mutex_lock(&rwType_pointer->r_mutex_t);
while(!rwType_pointer ->r_is_wait){
pthread_mutex_unlock(&rwType_pointer->r_mutex_t);
usleep(100);
pthread_mutex_lock(&rwType_pointer->r_mutex_t);
}
pthread_mutex_unlock(&rwType_pointer->r_mutex_t);
//进行通知读者,读者进行读取
pthread_cond_broadcast(&rwType_pointer->r_cond_t);
//写者进行上锁,将其设置为等待状态
pthread_mutex_lock(&rwType_pointer->w_mutex_t);
rwType_pointer->w_is_wait = 1;
pthread_cond_wait(&rwType_pointer->w_cond_t,&rwType_pointer->w_mutex_t);
pthread_mutex_unlock(&rwType_pointer->w_mutex_t);
}
return (void*)0;
}
/* *
*这个是读者的线程,先应该是读者进行阻塞,等待写者写完毕之后通知,然后读者去读,
读完之后,读者再去通知写者,写者再去继续写
* */
void *r_th(void* argv){
RWType * rwType_pointer = (void*)argv;
int i = 0;
while(i<80){
pthread_mutex_lock(&rwType_pointer->r_mutex_t);
rwType_pointer->r_is_wait = 1;
pthread_cond_wait(&rwType_pointer->r_cond_t,&rwType_pointer->r_mutex_t);
printf("%lx thread getresult:%d\n",pthread_self(),getIns(rwType_pointer));
pthread_mutex_unlock(&rwType_pointer->r_mutex_t);
//读完数据之后,检查写者的等待状态
pthread_mutex_lock(&rwType_pointer->w_mutex_t);
//当写者处于等待状态的时候,退出while
while(!rwType_pointer->w_is_wait){
pthread_mutex_unlock(&rwType_pointer->w_mutex_t);
usleep(100);
pthread_mutex_lock(&rwType_pointer->w_mutex_t);
}
pthread_mutex_unlock(&rwType_pointer->w_mutex_t);
//通知写者,让其继续进行写
pthread_cond_broadcast(&rwType_pointer->w_cond_t);
i++;
}
return (void*)0;
}
int main(int argc,char* argv[]){
pthread_t read_thread,write_thread;
RWType rwType;
rwType.ins = 0;
rwType.w_is_wait = 0;
rwType.r_is_wait = 0;
pthread_mutex_init(&rwType.w_mutex_t,NULL);
pthread_mutex_init(&rwType.r_mutex_t,NULL);
pthread_cond_init(&rwType.w_cond_t,NULL);
pthread_cond_init(&rwType.r_cond_t,NULL);
//创建两个线程
int res = 0;
if((res = pthread_create(&write_thread,NULL,w_th,(void*)&rwType))!=0){
perror("w thread create error");
}
if((res = pthread_create(&read_thread,NULL,r_th,(void*)&rwType))!=0){
perror("r thread create error");
}
pthread_join(read_thread,NULL);
pthread_join(write_thread,NULL);
//销毁条件变量和互斥锁
pthread_mutex_destroy(&rwType.r_mutex_t);
pthread_mutex_destroy(&rwType.w_mutex_t);
pthread_cond_destroy(&rwType.r_cond_t);
pthread_cond_destroy(&rwType.w_cond_t);
printf("main thread ended\n");
return 0;
}
上面就是简单的读者和写者之间的关系,逻辑上面可能有点负责,就是读者先写,写完之后通知读者去读,读者读完之后,再去通知写者继续去写.这样的一个反复执行的过程.
代码是可以直接进行run的.写的不好的地方,欢迎指出