Linux 学习记录26(进程线程篇)
一、多线程同步
所需头文件:#include <semaphore.h>
1. 概念
- 线程同步概念:已经知道线程的执行顺序,并且线程是顺序执行的
- 线程的同步机制适用于生产者消费者模型:在生产线上,例如线程1生产了一辆车,线程2购买这辆车,所以在线程执行时,必须线程1先执行,线程2再执行
2. 线程同步之无名信号量
无名信号量维护了一个value,生产者线程每生产一个,value就会自增1,消费者线程每消费一个,value就会自减1,当value为0时,生产者线程可以执行,但是消费者线程,阻塞等待。
(1. 无名信号量的API
定义信号量 :sem_t sem
>1初始化信号量
函数原型:int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化信号量
参数1:信号量指针
参数2:线程号
0:多线程
1:进程间
参数3:信号量格式,如果初始为1,则说明能够获取信号量,如果为0不能获取信号量
返回值:成功返回e,失败返回-1置位错误码
>2申请资源
函数原型:int sem_wait(sem_t *sem);
功能:申请资源(P操作 -1),如果申请不到资源,阻塞等待
参数1:信号量地址
返回值:成功返回0,失败返回-1,并置位错误码
>3释放资源
函数原型:int sem_post(sem_t *sem);
功能:释放资源(v操作 +1)
参数1:信号量指针
返回值:成功返回0,失败返回-1并置位错误码
>4销毁信号量
函数原型:int sem_destroy(sem t *sem);
功能:销毁信号量
参数1:信号量地址
返回值:成功返回e,失败返回-1置位错误码
(2. api的使用
sem_t sem1;//定义信号量
/*定义线程处理函数*/
void *task1(void *arg)
{
int num = 10;
while(num--)
{
sleep(2);//每两秒释放一次资源
printf("子线程1释放了一个资源\r\n");
sem_post(&sem1);//释放资源
}
printf("子进程1任务结束 关闭进程\r\n");
pthread_exit(NULL);//退出线程
}
====================================================================
/*定义线程处理函数*/
void *task2(void *arg)
{
int num = 10;
while(num--)
{
sem_wait(&sem1);//申请资源
printf("子线程2申请了一个资源\r\n");
}
printf("子进程2任务结束 关闭进程\r\n");
pthread_exit(NULL);//退出线程
}
===================================================================
int main(int argc, char const *argv[])
{
pthread_t tid1;//线程号
pthread_t tid2;//线程号
sem_init(&sem1,0,0);//初始化信号量
if(pthread_create(&tid1, NULL,task1,NULL) != 0)
{//如果创建失败
perror("pthread task1");
return -1;
}
if(pthread_create(&tid2, NULL,task2,NULL) != 0)
{//如果创建失败
perror("pthread task2");
return -1;
}
printf("主线程 \r\n");
printf("tid1: %#lx\ttid2: %#lx\r\n",tid1,tid2);
pthread_join(tid1,NULL);//阻塞等待线程回收
printf("线程1回收完成\r\n");
pthread_join(tid2,NULL);
printf("线程2回收完成\r\n");
pthread_exit(EXIT_SUCCESS);
return 0;
}
3. 线程同步之条件变量
在linux系统中,如果有多个线程需要同步的话,如果只使用无名信号量来完成,需要定义多个无名信号量,使用起来比较麻烦,此时我们可以采用系统开发的条件变量来完成这个工作
条件变量维护了一个队列,可以将多个线程先放入该队列中进行休眠,当有信号唤醒该线程时,那么该线程进入执行状态
(1. 条件变量的api
定义条件变量:pthread_cond t_cond = PTHREAD_COND_INITIALIZER:
>1动态初始化条件变量
函数原型:int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
功能:动态初始化一个条件变量
参数1:条件变量地址
参数2:条件变量属性,一般为NULL
返回值:成功返回0,失败返回非0的错误码
>2唤醒一个休眠的进程
函数原型:int pthread_cond_signal(pthread_cond_t *cond);
功能:唤醒一个休眠的进程,该进程在条件变量中进行休眠等待的
参数1:条件变量的地址
返回值:成功返回0,失败返回非0的错误码
>3唤醒所有在等待休眠的线程
函数原型: int pthread_cond_broadcast(pthread_cond_t *cond);
功能:唤醒所有在等待休眠的线程
参数1:条件变量的地址
返回值:成功返回0,失败返回非0的错误码
>4阻塞等待条件变量
函数原型: int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
功能:阻塞等待条件变量,在条件变量中维护了一个队列
参数1:条件变量地址
参数2:互斥锁,该锁的存在,就是为了解决在往队列中存放线程时,出现竞态用的
返回值:成功返回0,失败返回非0的错误码
>5销毁一个条件变量
函数原型: int pthread_cond_destroy(pthread_cond_t *cond);
功能:销毁一个条件变量
参数1:条件变量地址
返回值:成功返回0,失败返回非0的错误码
(2. 实现两个线程的同步
pthread_mutex_t mutex;//定义互斥锁
sem_t sem1;//定义信号量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//定义条件变量
/*定义线程处理函数*/
void *task1(void *arg)
{
int num = 10;
while(num--)
{
sleep(2);//每两秒释放一次资源
printf("子线程1释放了一个资源\r\n");
pthread_cond_signal(&cond);
}
printf("子进程1任务结束 关闭进程\r\n");
pthread_exit(NULL);//退出线程
}
/*定义线程处理函数*/
void *task2(void *arg)
{
int num = 10;
while(num--)
{
pthread_cond_wait(&cond,&mutex);
printf("子线程2申请了一个资源\r\n");
}
printf("子进程2任务结束 关闭进程\r\n");
pthread_exit(NULL);//退出线程
}
int main(int argc, char const *argv[])
{
pthread_t tid1;//线程号
pthread_t tid2;//线程号
pthread_cond_init(&cond,NULL);//动态初始化条件变量
sem_init(&sem1,0,0);//初始化信号量
if(pthread_create(&tid1, NULL,task1,NULL) != 0)
{//如果创建失败
perror("pthread task1");
return -1;
}
if(pthread_create(&tid2, NULL,task2,NULL) != 0)
{//如果创建失败
perror("pthread task2");
return -1;
}
printf("主线程 \r\n");
printf("tid1: %#lx\ttid2: %#lx\r\n",tid1,tid2);
pthread_join(tid1,NULL);//阻塞等待线程回收
printf("线程1回收完成\r\n");
pthread_join(tid2,NULL);
printf("线程2回收完成\r\n");
pthread_exit(EXIT_SUCCESS);
return 0;
}
(3. 使用条件变量实现多线程同步问题
pthread_mutex_t mutex;//定义互斥锁
sem_t sem1;//定义信号量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//定义条件变量
/*定义线程处理函数*/
void *task1(void *arg)
{
int num = 10;
while(num--)
{
sleep(2);//每两秒释放一次资源
pthread_mutex_lock(&mutex);//给临界区上锁
printf("子线程1释放了一个资源\r\n");
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);//给临界区解锁
}
printf("子进程1任务结束 关闭进程\r\n");
pthread_exit(NULL);//退出线程
}
/*定义线程处理函数*/
void *task2(void *arg)
{
int num = 10;
while(num--)
{
pthread_mutex_lock(&mutex);//给临界区上锁
pthread_cond_wait(&cond,&mutex);
printf("子线程[ %#lx ]申请了一个资源\r\n",pthread_self());
pthread_mutex_unlock(&mutex);//给临界区解锁锁
}
printf("子进程2任务结束 关闭进程\r\n");
pthread_exit(NULL);//退出线程
}
int main(int argc, char const *argv[])
{
pthread_t tid1;//线程号
pthread_t tid2;//线程号
pthread_t tid3;//线程号
pthread_t tid4;//线程号
pthread_t tid5;//线程号
pthread_mutex_init(&mutex,NULL);//初始化互斥锁
pthread_cond_init(&cond,NULL);//动态初始化条件变量
sem_init(&sem1,0,0);//初始化信号量
if(pthread_create(&tid1, NULL,task1,NULL) != 0)
{//如果创建失败
perror("pthread task1");
return -1;
}
if(pthread_create(&tid2, NULL,task2,NULL) != 0)
{//如果创建失败
perror("pthread task2");
return -1;
}
if(pthread_create(&tid3, NULL,task2,NULL) != 0)
{//如果创建失败
perror("pthread task3");
return -1;
}
if(pthread_create(&tid4, NULL,task2,NULL) != 0)
{//如果创建失败
perror("pthread task4");
return -1;
}
if(pthread_create(&tid5, NULL,task2,NULL) != 0)
{//如果创建失败
perror("pthread task5");
return -1;
}
printf("主线程 \r\n");
printf("tid1: %#lx\ttid2: %#lx\r\n",tid1,tid2);
pthread_join(tid1,NULL);//阻塞等待线程回收
printf("线程1回收完成\r\n");
pthread_join(tid2,NULL);
printf("线程2回收完成\r\n");
pthread_mutex_destroy(&mutex);//销毁锁
pthread_exit(EXIT_SUCCESS);
return 0;
}
二、进程间的通讯(IPC)
- 对于线程间的通信,我们可以使用全局变量来实现,因为多个线程共享进程的资源
- 由于进程的内存空间是独立的,所以不能通过全局变量来进行进程间的通信
- 虽然可以使用尾部文件来实现多个进程间的通信,但是进程不能同步
- 由于每个进程0-3G是独立的内存,共享3-4G的内核空间,所以可以通过3-4G的内核空间来完成多个线程间的通信。
- 系统提供的通信方式一共有三种
- 传统内核提供的通讯机制
无名管道
有名管道
信号(signal)
- system V提供的进程间的通信
消息队列
共享内存
信号灯集
- 套接字(socket)
管道
- 管道原理:在进程的3-4G的内核空间在创建的一个特殊文件,管道可以传输不同进程间的数据,而且管道中的数据直接保存在内存
- 管道的特点:
- 管道是一个特殊文件,其他文件都是存储在磁盘上,而管道上的数据存储在内存上
- 管道遵循先进先出原则,而且管道文件不能使用lseek
- 由于管道文件在内核空间创建,所以但凡使用管道文件,只能使用文件IO进行操作,不能使用标准IO
- 管道的读取操作是一次性的,也就是管道中的数据被读取后就从管道中删除了
- 管道通信是一种半双工的通信方式
单工:只能单向发送数据
半双工:同一时间内只允许一个方向发送数据
全双工:同一时间内可以双向发送数据
- 对于管道而言,会提供读和写的两端,如果两端都被关闭了,管道就关闭了
- 管道文件的大小:64Kb=65535b
- 管道的读写问题
1. 无名管道
1. 无名管道:没有名字的管道文件
2. 由于没有文件名,所以不能所以open函数打开,使用对应函数pipe函数时就可以打开
3. 因为没有名字,所以其他进程不能使用该管道,使用该通信方式只适用于亲缘进程间的通讯
4. 无名管道文件在系统文件中是不可见的,使用两个无关的进程是无法拿到同一个处理该文件的描述符的
(1. 无名管道API
>1 创建一个无名管道文件
函数原型:int pipe(int pipefd[2]);
功能:创建一个无名管道文件
参数1:文件描述符数组,创建好文件后会提供读写两个文件描述符,放到数组中
[0]:读端
[1]:写端
返回值:成功返回0,失败返回-1,并置位错误码
================================================================
例:
int pipdfd[2];
pipe(pipefd);
2. 有名管道
1. 创建的管道文件是有名字的,能够在文件系统中进行找到别打开
2. 因为该管道文件存在,导致能够使用管道文件来完成亲缘和非亲缘间的进程通信
(1. 有名管道api
函数原型:int mkfifo(const char *pathname, mode_t mode);
功能:创建一个有名管道文件
参数1:管道的名字
参数2:创建管道的权限
返回值:成功返回0,失败返回-1,并置位错误码
思维导图
练习 使用有名管道完成两个无亲缘进程的沟通
1. 进程1
(主函数
int main(int argc, char const *argv[])
{
/*创建管道文件*/
if(mkfifo("./fifo1",0664))
{//如果已创建就报错
perror("mkfifo");
}
pthread_t tid1;//线程号
pthread_t tid2;//线程号
if(pthread_create(&tid1, NULL,write_fifo,NULL) != 0)
{//如果创建失败
perror("pthread task1");
return -1;
}
if(pthread_create(&tid2, NULL,read_fifo,NULL) != 0)
{//如果创建失败
perror("pthread task2");
return -1;
}
pthread_join(tid1,NULL);//阻塞等待线程回收
printf("写fifio线程回收完成\r\n");
pthread_join(tid2,NULL);
printf("读fifio线程回收完成\r\n");
return 0;
}
(写线程
/*写管道进程*/
void *write_fifo(void *argv)
{
int fd = 0;
char str[128] = {0};
printf("正在打开管道文件fifo1...\r\n");
if((fd=open("./fifo1",Rp)) == -1)
{//只写打开管道文件1
perror("op_fifo1");
pthread_exit(NULL);//退出线程
}
printf("成功打开管道文件fifo1\r\n");
while(1)
{
fgets(str,sizeof(str),stdin);//获取字符串
str[strlen(str)-1] = '\0';
if(strcmp(str,"over") == 0)
{
write(fd,str,strlen(str));//写入管道
break;
}
printf("向对方发送: %s\r\n",str);
write(fd,str,strlen(str));//写入管道
}
close(fd);
printf("写fifo任务结束 关闭进程\r\n");
pthread_exit(NULL);//退出线程
}
(读线程
/*读管道进程*/
void *read_fifo(void *argv)
{
int fd = 0;
char str[128] = {0};
printf("正在打开管道文件fifo2...\r\n");
if((fd=open("../test2/fifo2",Rp)) == -1)
{//只读打开管道文件2
perror("op_fifo2");
pthread_exit(NULL);//退出线程
}
printf("成功打开管道文件fifo2\r\n");
while (1)
{
memset(str,0,sizeof(str));//数组清零
read(fd,str,sizeof(str));
printf("从对方接收: %s\r\n",str);
if(strcmp(str,"over") == 0)
{
printf("对方已关闭会话\r\n");
break;
}
}
close(fd);
printf("读fifo任务结束 关闭进程\r\n");
pthread_exit(NULL);//退出线程
}
(头文件
#ifndef __PUBLIC_H_
#define __PUBLIC_H_
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/wait.h>
#include <pthread.h>
#include <semaphore.h>
#define R O_RDONLY
#define W O_WRONLY|O_CREAT|O_TRUNC
#define A O_WRONLY|O_CREAT|O_APPEND
#define Rp O_RDWR
#define Wp O_RDWR|O_CREAT|O_TRUNC
#define Ap O_RDWR|O_CREAT|O_APPEND
/*写管道进程*/
void *write_fifo(void *argv);
/*读管道进程*/
void *read_fifo(void *argv);
#endif
2. 进程2
(主函数
int main(int argc, char const *argv[])
{
/*创建管道文件*/
if(mkfifo("./fifo2",0664))
{//如果已创建就报错
perror("mkfifo");
}
pthread_t tid1;//线程号
pthread_t tid2;//线程号
if(pthread_create(&tid1, NULL,write_fifo,NULL) != 0)
{//如果创建失败
perror("pthread task1");
return -1;
}
if(pthread_create(&tid2, NULL,read_fifo,NULL) != 0)
{//如果创建失败
perror("pthread task2");
return -1;
}
pthread_join(tid1,NULL);//阻塞等待线程回收
printf("写fifio线程回收完成\r\n");
pthread_join(tid2,NULL);
printf("读fifio线程回收完成\r\n");
return 0;
}
(写线程
/*写管道进程*/
void *write_fifo(void *argv)
{
int fd = 0;
char str[128] = {0};
printf("正在打开管道文件fifo2...\r\n");
if((fd=open("./fifo2",Rp)) == -1)
{//只写打开管道文件1
perror("op_fifo2");
pthread_exit(NULL);//退出线程
}
printf("成功打开管道文件fifo2\r\n");
while(1)
{
fgets(str,sizeof(str),stdin);//获取字符串
str[strlen(str)-1] = '\0';
if(strcmp(str,"over") == 0)
{
write(fd,str,strlen(str));//写入管道
break;
}
printf("向对方发送: %s\r\n",str);
write(fd,str,strlen(str));//写入管道
}
close(fd);
printf("写fifo任务结束 关闭进程\r\n");
pthread_exit(NULL);//退出线程
}
(读线程
/*读管道进程*/
void *read_fifo(void *argv)
{
int fd = 0;
char str[128] = {0};
printf("正在打开管道文件fifo1...\r\n");
if((fd=open("../test1/fifo1",Rp)) == -1)
{//只读打开管道文件2
perror("op_fifo1");
pthread_exit(NULL);//退出线程
}
printf("成功打开管道文件fifo2\r\n");
while (1)
{
memset(str,0,sizeof(str));//数组清零
read(fd,str,sizeof(str));
printf("从对方接收: %s\r\n",str);
if(strcmp(str,"over") == 0)
{
printf("对方已关闭会话\r\n");
break;
}
}
close(fd);
printf("读fifo任务结束 关闭进程\r\n");
pthread_exit(NULL);//退出线程
}
(头文件
#ifndef __PUBLIC_H_
#define __PUBLIC_H_
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/wait.h>
#include <pthread.h>
#include <semaphore.h>
#define R O_RDONLY
#define W O_WRONLY|O_CREAT|O_TRUNC
#define A O_WRONLY|O_CREAT|O_APPEND
#define Rp O_RDWR
#define Wp O_RDWR|O_CREAT|O_TRUNC
#define Ap O_RDWR|O_CREAT|O_APPEND
/*写管道进程*/
void *write_fifo(void *argv);
/*读管道进程*/
void *read_fifo(void *argv);
#endif