文章目录
- 一、 线程的同步
- (一)无名信号量sem
- 1. 定义和初始化
- 2.获取信号量
- 3.释放信号量
- 4. 销毁
- 5. 使用示例
- (二)条件变量
- 1. 定义和初始化
- 2. 获取条件变量
- 3. 释放条件变量
- 4. 销毁条件变量
- 二、进程间通信
- (一)无名管道
- 1.概念
- 2. 定义
- 3. 特点
- (二)有名管道
- 1. 原理
- 2. 定义
- 3. 特点
- (三)信号通信
- 1. 概念
- 2. 定义
一、 线程的同步
线程同步:提前已经知道了线程应该有的执行的顺序,控制线程按指定的顺序执行
互斥锁无法保证线程执行的顺序,一个线程解锁后无法保证是另一个线程上锁,有可能仍是原来刚解锁的线程又再次上锁
无名信号量是荷兰计算器科学家发明的,PV操作的PV来自荷兰语。
(一)无名信号量sem
1. 定义和初始化
#include <semaphore.h>
定义无名信号量
sem_t sem;
初始化无名信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化无名信号量
参数:
sem:无名信号量指针
pshared:
0:两个线程间同步
非0:两个进程间同步
value:信号量的初始值
如果是1表示可以获取信号量
如果是0表示不可以获取信号量
返回值:
成功 0
失败 -1 重置错误码
2.获取信号量
获取信号量(P操作)
int sem_wait(sem_t *sem);
功能:获取信号量 (将信号量的值-1)
如果信号量的值已经是0了,则sem_wait会阻塞,等到能执行减1操作为止
参数:sem:无名信号量指针
返回值:
成功 0
失败 不会改变信号量的值 返回 -1 重置错误码
3.释放信号量
释放信号量(V操作)
int sem_post(sem_t *sem);
功能:释放信号量(将信号量的值+1)
参数:sem:无名信号量指针
返回值:
成功 0
失败 不会改变信号量的值 返回 -1 重置错误码
4. 销毁
销毁无名信号量
int sem_destroy(sem_t *sem);
功能:销毁无名信号量
参数:sem:无名信号量指针
返回值:
成功 0
- 注:
- 无名信号量不允许减到小于1,当等于0时,sem_wait会阻塞等待;
- 但是无名信号量允许加到大于1
5. 使用示例
现有三个线程,其功能分别为打印A、打印B、打印C,使用无名信号量使其按照ABC的顺序打印。
#include <my_head.h>
sem_t sem1;
sem_t sem2;
sem_t sem3;
//A线程
void *task_func_1(void *arg){
while(1){
sem_wait(&sem1);
sleep(1);
printf("A ");
fflush(stdout);
sem_post(&sem2);
}
}
//B线程
void *task_func_2(void *arg){
while(1){
sem_wait(&sem2);
sleep(1);
printf("B ");
fflush(stdout);
sem_post(&sem3);
}
}
//C线程
void *task_func_3(void *arg){
while(1){
sem_wait(&sem3);
sleep(1);
printf("C ");
fflush(stdout);
sem_post(&sem1);
}
}
int main(int argc, const char *argv[])
{
//初始化无名信号量
sem_init(&sem1, 0, 1);
sem_init(&sem2, 0, 0);
sem_init(&sem3, 0, 0);
pthread_t tid1 = 0, tid2 = 0, tid3=0;
int ret = 0;
if(0 != (ret = pthread_create(&tid1, NULL, task_func_1, NULL))){
printf("pthread_create error : %s\n", strerror(ret));
exit(-1);
}
if(0 != (ret = pthread_create(&tid2, NULL, task_func_2, NULL))){
printf("pthread_create error : %s\n", strerror(ret));
exit(-1);
}
if(0 != (ret = pthread_create(&tid3, NULL, task_func_3, NULL))){
printf("pthread_create error : %s\n", strerror(ret));
exit(-1);
}
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
//销毁无名信号量
sem_destroy(&sem1);
sem_destroy(&sem2);
sem_destroy(&sem3);
return 0;
}
(二)条件变量
无名信号量适合线程数比较少的线程中实现微观的同步过程,
条件变量更适用于大量线程实现同步
1. 定义和初始化
#include <pthread.h>
pthread_cond_t cond;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//静态初始化
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
功能:动态初始化条件变量
参数:
cond:条件变量指针
cond_attr:条件变量的属性 NULL 表示使用默认属性
返回值:
成功 0 失败 错误码
2. 获取条件变量
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
功能:获取条件变量
参数:
cond:条件变量指针
mutex:互斥锁指针
返回值:
成功 0 失败 错误码
使用流程:
1.先获取到互斥锁
(可以先完成一些任务初始化的工作)
2.调用pthread_cond_wait
2.1 将当前线程添加到队列中
2.2 解锁
2.3 在队列中休眠
2.4 重新获取锁
这时,如果等待的条件没有发生,会继续解锁、休眠
如果等待的条件发生了,会将线程在队列中移除
3.执行后续的任务
4.解锁
3. 释放条件变量
int pthread_cond_signal(pthread_cond_t *cond);
功能:释放一个条件变量,唤醒一个等待条件的线程
参数:cond:条件变量指针
返回值:成功 0 失败 错误码
int pthread_cond_broadcast(pthread_cond_t *cond);
功能:释放所有的资源,唤醒所有等待条件的线程
参数:cond:条件变量指针
返回值:成功 0 失败 错误码
4. 销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
功能:销毁条件变量
参数:cond:条件变量指针
返回值:成功 0 失败 错误码
二、进程间通信
进程间通信方式
1.传统进程间通信
无名管道
有名管道
信号通信
2.system V 版本引入了IPC进程间通信
消息队列
共享内存
信号灯集
socket套接字通信
(一)无名管道
1.概念
在使用fork函数创建子进程前打开的文件描述符,在fork之后,子进程会继承父进程打开的文件描述符。
无名管道是内核空间实现的机制,只能用于亲缘进程间通信,无名管道的大小是64K。
2. 定义
#include <unistd.h>
int pipe(int pipefd[2]);
功能:
创建一个管道 一个单向的数据通道 可用于进程间通信
数组 pipefd 中会返回两个文件描述符,
pipefd[0] 管道的读端 pipefd[1] 管道的写端
写入管道的数据会被内核缓冲 直到被读走
参数:pipefd:保存管道的两个端点的文件描述符的数据
返回值:
成功 0
失败 -1 重置错误码
3. 特点
1.只能用于亲缘间进程的通信
2.无名管道数据半双工的通信的方式
单工 : A -------------->B
半双工 : 同一时刻 A----->B B------>A
全双工 : 同一时刻 A<---->B
3.无名管道的大小是64K
4.无名管道不能够使用lseek函数(调用会出错 返回 -1)
5.读写的特点
如果读端存在 写管道:有多少写多少,直到写满为止(64k)写阻塞,
直到管道中腾出新的4K空间,写操作解除阻塞
如果读端不存在 写管道,管道破裂(SIGPIPE)
如果写端存在 读管道:有多少读多少,没有数据的时候阻塞等待
如果写端不存在 读管道:有多少读多少,没有数据的时候立即返回(非阻塞)
(二)有名管道
1. 原理
有名管道会在文件系统中创建一个管道文件,只需要打开这个文件,进行读写操作即可。
有名管道是在文件系统中映射出一个管道文件名,管道文件本质是在内存上的,在硬盘上的只是一个标识。
2. 定义
mkfifo命令也可以创建管道文件
管道文件不能重名
在管道文件中写入内容,文件大小并不会增加,因为写入管道文件的内容保存在内存中,并非硬盘中
//也可以在终端上使用 mkfifo 命令创建管道文件
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
功能:
参数:
pathname:管道文件的路径和名字
mode:权限 & ~umask --》 最终权限
返回值:
成功 0
失败 -1 重置错误码
3. 特点
- 可以用于任意进程间的通信,不仅限亲缘进程
- 有名管道数据是半双工的通信方式
- 有名管道的大小是64K
- 有名管道不能够使用lseek函数(调用会失败 返回 -1)
- 读写的特点
如果读端存在写管道:有多少写多少,直到写满为止(64k)写阻塞
如果读端不存在写管道
1.读端没有打开,写端在open的位置阻塞
2.读端打开后关闭,管道破裂(SIGPIPE)
如果写端存在读管道:有多少读多少,没有数据的时候阻塞等待
如果写端不存在读管道
1.写端没有打开,读端在open的位置阻塞
2.写端打开后关闭,有多少读多少,没有数据的时候立即返回
(三)信号通信
1. 概念
信号是中断的一种软件模拟,中断是基于硬件的概念,信号是基于linux内核实现的。
用户可以给进程发信号,进程可以给进程发信号,linux内核也可以给进程发信号。
信号的处理方式有三种:默认DEF、忽略IGN、捕捉 caught
man 7 signal
信号查看:kill -l
2. 定义
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:
注册信号和信号处理方式的关系
参数:
signum:信号的编号
handler:处理方式
SIG_IGN 忽略
SIG_DFL 默认
也可以传一个函数 捕捉
void sig_func(int signum){
//自定义的逻辑
}
返回值:
成功 返回handler
失败 SIG_ERR 重置错误码
signal函数只是注册了信号和处理方式的关系,并不会阻塞等待信号产生