一,多线程互斥
1.1多线程竞态现象
临界资源:多个线程共享的资源叫临界资源
临界区:访问临界资源的代码就叫临界区
互斥:有你没我的行为,同一时间内,只允许一个线程拥有临界资源
同步:要求按顺序执行,按顺序访问临界资源
1.2多线程互斥
1.3线程互斥锁API
二,线程同步
2.1
概念:已经知道线程的执行顺序,并且线程是顺序执行的
线程的同步机制适用于生产者消费者模型:在生产线上,例如线程1生产了1辆车,线程2购买这辆车,所以在线程1执行时,线程2在执行
2.2线程同步之无名信号量
无名信号量维护了一个value,生产者线程每生产一个,value就会自增1,消费者线程每消费一个,value-1,当value为0时,生产者线程可以执行。但是消费者线程,阻塞等待。
2.2.1无名信号量的API
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化信号量
参数1:信号量的指针
参数2:线程号 0表示多线程,1表示进程间
参数3:信号量的个数,如果初始为1,则说明能够获取信号量,如果为0,说明不能获取信号量
返回值:成功返回0,失败返回-1,并置位错误码
#include <semaphore.h>
int sem_wait(sem_t *sem);
功能:申请资源(p资源-1),如果申请不到资源,阻塞等待
参数:信号量地址
返回值:成功返回0,失败返回-1并置位错误码
#include <semaphore.h>
int sem_post(sem_t *sem);
功能:释放资源(v+1)
参数:信号量指针
返回值:成功返回0,失败返回-1并置位错误码
#include <semaphore.h>
int sem_destroy(sem_t *sem);
功能:销毁信号量
参数:信号量地址
返回值
成功返回0,失败返回-1并置位错误码
2.2.2 实例(生产者消费者模型)
#include <myhead.h>
#include <semaphore.h>
sem_t sem; //定义一个无名信号量
//定义一个生产者线程处理函数
void *task1(void *arg)
{
int num=5;
while(num--)
{
sleep(2);
printf("我生产了一辆小鹏汽车\n");
//释放资源
sem_post(&sem);
}
pthread_exit(NULL);//退出线程
}
//定义一个消费者线程处理函数
void *task2(void *arg)
{
int num=5;
while(num--)
{
//3.申请资源
sem_wait(&sem);
printf("我消费了一辆小鹏汽车\n");
}
pthread_exit(NULL);//退出线程
}
int main(int argc,const char *argv[])
{
pthread_t tid1,tid2;//定义两个线程
//创建两个线程
//2.初始化无名信号量
sem_init(&sem,0,0);
//第一个0表示用于多线程,第二个0表示value值为0
if(pthread_create(&tid1,NULL,task1,NULL))
{
perror("create tid1");
return -1;
}
if(pthread_create(&tid2,NULL,task2,NULL))
{
perror("create tid2");
return -1;
}
printf("tid1=%#lx,tid2=%#lx\n",tid1,tid2);
//阻塞等待回收子线程资源
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
return 0;
}
顺序打印5次ABC
#include <myhead.h>
#include <semaphore.h>
sem_t sem1; //定义三个无名信号量
sem_t sem2;
sem_t sem3;
//定义一个生产者线程处理函数
void *task1(void *arg)
{
int num=5;
while(num--)
{
//3.申请资源
sem_wait(&sem3);
sleep(2);
putchar('A');
//4释放资源
sem_post(&sem1);
}
pthread_exit(NULL);//退出线程
}
//定义一个消费者线程处理函数
void *task2(void *arg)
{
int num=5;
while(num--)
{
//3.申请资源
sem_wait(&sem1);
printf("B");
//4释放资源
sem_post(&sem2);
}
pthread_exit(NULL);//退出线程
}
void *task3(void *arg)
{
int num=5;
while(num--)
{
//3.申请资源
sem_wait(&sem2);
printf("C");
//4释放资源
sem_post(&sem3);
}
pthread_exit(NULL);//退出线程
}
int main(int argc,const char *argv[])
{
pthread_t tid1,tid2,tid3;//定义两个线程
//创建两个线程
//2.初始化无名信号量
sem_init(&sem1,0,0);
sem_init(&sem2,0,0);
sem_init(&sem3,0,1);
//第一个0表示用于多线程,第二个0表示value值为0
if(pthread_create(&tid1,NULL,task1,NULL))
{
perror("create tid1");
return -1;
}
if(pthread_create(&tid2,NULL,task2,NULL))
{
perror("create tid2");
return -1;
}
if(pthread_create(&tid3,NULL,task3,NULL))
{
perror("create tid3");
return -1;
}
printf("tid1=%#lx,tid2=%#lx,tid3=%#lx\n",tid1,tid2,tid3);
//阻塞等待回收子线程资源
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);
return 0;
}
2.3线程同步之条件变量
在linux系统中,如果多个线程需要同步的话,如果只使用无名信号量来完成,需要定义多个无名信号量,使用起来比较麻烦,我们可以采用系统开发的条件变量完成
条件变量维护了一个队列,可以将多个线程先放入该队列中进行休眠,当有信号唤醒该线程,那么该线程进入执行状态
2.3.1条件变量的API
#include <myhead.h>
#include <semaphore.h>
//12定义一个条件变量并初始化
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;//静态初始化
//定义互斥锁
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
void *task1(void *arg)
{
int num=5;
while(num--)
{
sleep(2);
puts("生产了一辆小鹏汽车");
//4.唤醒休眠进程,发送信号
pthread_cond_signal(&cond);
}
pthread_exit(NULL);//退出线程
}
//定义一个消费者线程处理函数
void *task2(void *arg)
{
int num=5;
while(num--)
{
//3.阻塞等待生产者发送信号
pthread_cond_wait(&cond,&mutex);
puts("消费了一辆小鹏汽车");
}
pthread_exit(NULL);//退出线程
}
int main(int argc,const char *argv[])
{
pthread_t tid1,tid2;//定义两个线程
//创建两个线程
//第一个0表示用于多线程,第二个0表示value值为0
if(pthread_create(&tid1,NULL,task1,NULL))
{
perror("create tid1");
return -1;
}
if(pthread_create(&tid2,NULL,task2,NULL))
{
perror("create tid2");
return -1;
}
printf("tid1=%#lx,tid2=%#lx\n",tid1,tid2);
//阻塞等待回收子线程资源
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_cond_destory(&cond);
return 0;
}
2.3.2实现两个线程的同步
2.3.4 条件变量的使用场景
例如:编写12306的购票系统,服务器会给每个客户端开启一个线程用来服务客户,但是,如果当客户端发来连接请求时,才进行创建线程,会造成时间和效率问题,原因是,当有大量的客户端同时连接服务时,创建的线程会很慢
此时,我们可以使用条件变量来解决。在启动服务器时可以创建多个线程,将其放入队列中
作业:
使用多线程拷贝同一份文件,线程1拷贝前一半,线程2拷贝后一半,主线程阻塞等待回收子线程的资源
#include <myhead.h>
//临界资源
struct info
{
int fd_r;
int fd_w;
off_t size;
};
//互斥锁定义及初始化
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
void *callback1(void *arg)//函数的参数是主函数调用里的回调值
{
int fd_r=((struct info*)arg)->fd_r;//初始化文件描述符
int fd_w=((struct info*)arg)->fd_w;
off_t size=((struct info*)arg)->size;//相当于传参
pthread_mutex_lock(&mutex);//上锁
lseek(fd_r,0,SEEK_SET);
lseek(fd_w,0,SEEK_SET);
char c=0;
int i;
for(i=0;i<size/2;i++)//拷贝
{
read(fd_r,&c,1);
write(fd_w,&c,1);
}
pthread_mutex_unlock(&mutex);//关锁
printf("前半部分拷贝完毕\n");
pthread_exit(NULL);
}
void *callback2(void *arg)//函数的参数是主函数调用里的回调值
{
int fd_r=((struct info*)arg)->fd_r;//初始化文件描述符
int fd_w=((struct info*)arg)->fd_w;
off_t size=((struct info*)arg)->size;//相当于传参
pthread_mutex_lock(&mutex);//上锁
lseek(fd_r,size/2,SEEK_SET);
lseek(fd_w,size/2,SEEK_SET);
char c=0;
int i;
for(i=0;i<size-size/2;i++)//拷贝
{
read(fd_r,&c,1);
write(fd_w,&c,1);
}
pthread_mutex_unlock(&mutex);//关锁
printf("前半部分拷贝完毕\n");
pthread_exit(NULL);
}
int main(int argc,const char *argv[])
{
//创建互斥锁
pthread_mutex_init(&mutex,NULL);
//打开两个文件
int fd_r=open(argv[1],O_RDONLY);
if(fd_r<0)
{
perror("open file");
return -1;
}
int fd_w=open(argv[2],O_RDONLY);
if(fd_w<0)
{
perror("open file");
return -1;
}
off_t size=lseek(fd_r,0,SEEK_END);
printf("size=%ld\n",size);
//需要传入到线程中规定数据
struct info msg;
msg.fd_r=fd_r;
msg.fd_w=fd_w;
msg.size=size;
//创建两个线程用于拷贝文件
pthread_t tid1,tid2;
if(pthread_create(&tid1,NULL,callback1,&msg)!=0)
{
perror("pthread_create");
return -1;
}
if(pthread_create(&tid2,NULL,callback2,&msg)!=0)
{
perror("pthread_create");
return -1;
}
//关闭文件
close(fd_r);
close(fd_w);
//销毁互斥锁
pthread_mutex_destroy(&mutex);
return 0;
}