一、线程同步
原因:线程之间拥有许多共享资源,当此资源被多个线程进行访问时容易造成数据错误,程序崩溃,所以需要对线程的临界资源进行同步控制。
同步:多进程和多线程访问临界资源时,需要进行同步控制。多线程或者多进程的执行并不会完全的并行运行,有可能主线程需要等待函数线程的某些条件的发生。
多线程之间临界资源:
全局数据 堆区数据文件描述符
线程之间的同步控制方式:
3.1信号量
#include<semaphore.h>
以下四个函数均成功返回0,错误返回-1;
初始化:Int sem_init(sem_t *sem,int shared,int value);
sem为指向信号量结构的一个指针
share代表控制信号量的类型,如果值为0,代表这个信号量是当前进程的信号量,否则为多个进程共享
value为信号量的初始值
P操作:Int sem_wait(sem_t *sem);
V操作:Int sem_post(sem_t *sem);
删除:Int sem_destory(sem_t *sem);
代码示例:创建一个线程,主线程循环写数据,函数线程循环读数据
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
#include<semaphore.h>
sem_t sempth;//创建一个信号量
char buff[128];
void * fun(void *argv)//函数线程
{
sem_wait(&sempth);
while(1)
{
// sem_wait(&sempth);
printf("fun running\n");
printf("buff=%s\n",buff);
if(strncmp(buff,"end",3)==0)
{
break;
}
sem_wait(&sempth);
}
pthread_exit(NULL);
}
void main()
{
int sems=sem_init(&sempth,0,0);//将信号量初始化为0
assert(sems==0);
void *thread_result;
pthread_t id;
pthread_create(&id,NULL,fun,NULL);//线程的创建
while(1)//主线程
{
printf("main running\n");
printf("please input (if you input end ,the task end):\n");
fgets(buff,127,stdin);
if(strncmp(buff,"end",3)==0)
{
break;
}
sem_post(&sempth);
sleep(1);
}
int r=sem_destroy(&sempth);//清理信号量
}
运行结果:
此程序应注意的几个细节:
1、在对信号量进行初始化设置及pv操作时,首先考虑如果信号量初始值设为1,主线程与函数线程分别进行p、v操作,这样不能保证是哪条线程会先获得信号量,而我们所要控制的是主线程写端首先得到信号量,之后函数线程获得信号量进行读操作,与预期结果不符。
2、在确定信号量初始值为0的情况下,我们在想怎么使主线程先运行,我开始想到的是给主线程先进行一次v操作,紧接着进行一次p操作,最后主线程写完后再进行v操作,函数线程里先进行v操作再读完之后进行p操作。首先主线程和函数线程是创建之后在没有阻塞的情况下自发的同时运行,对主线程进行连续vp操作没有意义,再者函数线程里在读完之后如若进行p操作,这样的话函数线程完全可以自我满足,也就是自己可以v,然后自己p,这样就会让函数线程忽视主线程的存在。所以我们只让主线程有v的权利。
所以在设置时只需要在主线程度写完后设置一个v操作,使函数线程运行起来,在函数线程里,进入循环后为了保证函数线程每次在主线程写完之后读,在函数线程读完之后再进行一次p操作,直到主线程写完进行p操作,函数线程的循环体才能继续读取数据。还需要注意一点的是进入函数线程时的p操作是要放在循环体外的,如有人放在循坏体内就会产生这样的结果:除第一次外,每次读取数据需要主线程两次写入,也就是两次v操作,原因自己看代码就明白了。
3.2互斥锁
如果一个线程对临界资源完成加锁操作,则另一个线程怎样都不得访问这个资源。以下函数均为成功返回0,失败返回错误代码,但这些函数并不设置errno,必须进行返回值检查
初始化:int pthread_mutex_init(pthread_mutex_t *mutex,pthread_mutex_attr_t *attr)
第二个参数为锁的属性,一般设为NULL,使用其默认行为
加锁:int pthread_mutex_lock(pthread_mutex_t *mutex)
解锁:int pthread_mutex_unlock(pthread_mutex_t *mutex)
释放:int pthread_mutex_destroy(pthread_mutex_t *mutex)
代码示例:完成 创建一个线程,主线程循环写数据,函数线程循环读数据
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
#include<semaphore.h>
pthread_mutex_t mmutex;
char buff[128];
int time_s=0;//作用:因为是在函数线程里判断用户输入的“end”,所以这个变量用来判断函数线程是否结束
void * fun(void *argv)
{
sleep(1);//让主线程先获取锁先执行
while(1)
{
pthread_mutex_lock(&mmutex);
if(strncmp(buff,"end",3)==0)
{
break;
}
printf("fun running\n");
printf("buff=%s\n",buff);
buff[0]='\0';//函数线程如果工作(在此例中为输出buff)完成,则把buff[0]置为“\0”,用作后面的判断
pthread_mutex_unlock(&mmutex);
sleep(1);
pthread_mutex_lock(&mmutex);
while(buff[0]=='\0')//如果函数工作完成,睡眠一秒后再次获锁时失败,证明主线程拥有锁,也就是主函数中用户还未输出完成,就会阻塞
{
pthread_mutex_unlock(&mmutex);
sleep(1);
pthread_mutex_lock(&mmutex);//当函数线程再次可以获锁时,判断函数线程是否已经工作完成,若未完成,则完成输出工作
}
}
pthread_mutex_lock(&mmutex);
time_s=1;
buff[0]='\0';
pthread_mutex_unlock(&mmutex);
pthread_exit(NULL);
}
void main()
{
int res;
pthread_t id;
res=pthread_mutex_init(&mmutex,NULL);
assert(res==0);
res=pthread_create(&id,NULL,fun,NULL);
pthread_mutex_lock(&mmutex);
while(!time_s)//判断函数线程是否已经完成
{
printf("main running\n");
printf("please input (if you input end ,the task end):");
fgets(buff,127,stdin);
fflush(stdout);
pthread_mutex_unlock(&mmutex);
while(1)
{
pthread_mutex_lock(&mmutex);
if(buff[0]=='\0')//如果buff[0]=='\0',代表函数线程已完成输出工作,则用户继续输入
{
break;
}
else//否则等待函数线程
{
pthread_mutex_unlock(&mmutex);
sleep(1);//等待函数线程统计完成
}
pthread_mutex_unlock(&mmutex);
}
}
pthread_mutex_destroy(&mmutex);
}
运行结果:
注意:在读取、判断、修改共享数据时必须要加锁,以免读到错误数据(脏数据)
3.3条件变量
条件变量,它是发送信号与等待信号。互斥锁用户上锁,条件变量则用于等待。因为我们在线程/进程同步时,通常都需要等待其他线程/进程完成任务再继续自己的任务。这时,仅仅使用mutex就无法完美的解决这个问题。假设我们只使用mutex,则需要锁上一个mutex然后查询,如果没有前序任务则解锁,然后隔断时间再继续上述过程。
而借助条件变量,我们则可以大大简化这个过程。一般来说,在一个进程/线程中调用pthread_cond_wait()等待某个条件的成立,此时该进程阻塞在这里,另外一个进程/线程进行某种操作,当某种条件成立时,调用pthread_cond_signal(..)来发送信号,从而使pthread_cond_wait(..)返回。
常用函数:
#include <pthread.h>
//pthread_mutex_t 是条件变量的定义
static pthread_cond_t lock = PTHREAD_COND_INITIALIZER;
//等待条件变量 cptr
int pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr);
//发送条件变量 cptr
int pthread_cond_signal(pthread_cond_t *cptr)