220 lines
7.1 KiB
Markdown
220 lines
7.1 KiB
Markdown
# 并发
|
||
|
||
- 同步
|
||
- 异步:到来的时刻不确定
|
||
|
||
异步事件的处理:
|
||
|
||
1. 查询法:事件发生的频率高,查询方式复杂
|
||
2. 通知法:事件发生的频率低,通知方式简单
|
||
|
||
## 信号(初步异步)
|
||
|
||
### 信号的概念
|
||
>
|
||
> 信号是软件中断。
|
||
信号的响应依赖于中断。
|
||
|
||
```bash
|
||
kill -l # 查看信号列表
|
||
```
|
||
|
||
`core`文件:错误现场的保存。
|
||
对于个人用户,默认`core`文件大小为0,可通过`ulimit -c`设置。
|
||
|
||
### `signal()`函数
|
||
|
||
```c
|
||
#include <signal.h>
|
||
|
||
typedef void (*sighandler_t)(int);
|
||
|
||
/**
|
||
* signal - 设置信号处理函数
|
||
* @signum: 信号编号
|
||
* @handler: 信号处理函数
|
||
*
|
||
* 返回值:原信号处理函数
|
||
*/
|
||
sighandler_t signal(int signum, sighandler_t handler);
|
||
|
||
//* 不使用typedef, 直接声明函数原型
|
||
void (*signal(int signum,void (*func)(int)))(int);
|
||
// void (*signal(...)):这表示signal函数的返回类型是一个指向函数的指针,该函数返回类型为void。
|
||
// (int signum, void (*func)(int)):这是signal函数的参数列表,其中signum是一个整数类型的参数,func是一个指向参数为整数类型、返回类型为void的函数的指针。
|
||
// (int):这个函数指针所指向的函数接受一个整数类型的参数。
|
||
```
|
||
|
||
**信号会打断阻塞的系统调用。**
|
||
`write`,`read`,`open`,`fork`等系统调用都可能被信号打断。
|
||
故要判断返回值是不是`EINTR`,如果是,则表示信号被打断,需要重新调用。
|
||
|
||
### 信号的不可靠
|
||
|
||
在 Linux 中,信号被用来通知进程发生了某些事件,例如终端用户按下了中断键(Ctrl+C),或者一个进程运行时间过长等。但是信号被称为“不可靠”的,主要是因为以下几个原因:
|
||
|
||
1. **丢失信号**:在传统的 Unix 实现中,同一种信号类型如果在处理前多次发生,可能会被合并,只传递一次。这意味着除了第一次之外的其他信号都会被丢弃。例如,如果一个程序几乎同时收到两个 `SIGINT` 信号,它可能只能感知到一个。
|
||
|
||
2. **不可预测的顺序**:如果多个不同的信号几乎同时到达,它们被递送到进程的顺序可能与实际发生的顺序不同,这会使得程序的行为难以预测。
|
||
|
||
3. **中断系统调用**:在早期的 Unix 系统中,当信号被捕获时,正处于阻塞状态的系统调用(如 read, write, select 等)可能会被中断并提前返回,这常常需要额外的错误处理逻辑来重新发起系统调用。
|
||
|
||
4. **异步性**:信号是异步的,这意味着它们可以在程序执行的任何时刻到达。如果信号处理函数(signal handler)不够简单,它可能在执行程序的中间阶段被调用,而此时程序可能处于一个不一致的状态。因此,信号处理函数需要非常小心地编写,通常只能执行异步信号安全的函数。
|
||
|
||
为了克服信号的不可靠性,现代操作系统和库引入了新的机制,比如 `sigaction` API 允许更精细的控制信号处理,以及使用 `pselect` 或 `epoll` 等函数的组合来避免系统调用被中断的问题。此外,一些高级语言或框架提供了更高层次的抽象,使得信号处理变得更为安全和可靠。
|
||
|
||
### 可重入函数
|
||
|
||
所有的系统调用都是可重入的,一部分库函数也是可重入的,比如`memcpy`
|
||
|
||
### 信号的响应过程
|
||
|
||
每个进程至少两个位图,`mask`和`pending`,一般都是32位的。
|
||
`mask`是当前进程阻塞的信号集合,`pending`是当前进程收到的信号集合。
|
||
|
||
`mask && pending`得到的是当前进程需要处理的信号集合。
|
||
|
||
信号从收到到响应有一个不可避免的延迟。
|
||
|
||
标准信号的响应没有严格的顺序。
|
||
|
||
屏蔽信号就是通过`mask`置位来屏蔽信号。
|
||
|
||
M P
|
||
1 0 常规状态
|
||
收到信号,未处理
|
||
1 1
|
||
从内核态回用户态,M & P = 1,处理信号
|
||
0 0
|
||
处理信号前,M和P都置0
|
||
响应完后,M置1,再次对M & P进行判断。
|
||
有则重复,无则将M置1,回到常规状态。
|
||
1 0
|
||
|
||
### 常用函数
|
||
|
||
```c
|
||
/**
|
||
* kill - 向进程发送信号
|
||
* @param: pid: 进程ID:
|
||
* >0 : 进程ID
|
||
* =0 : 进程组ID (组内广播)
|
||
* =-1: 给当前进程有权限发信号的所有进程 (除了init进程)
|
||
* < -1: 发送给|pid|的进程 (eg. -5 就发送给 5号进程)
|
||
*
|
||
* @note:
|
||
* 这里的pid用法可以联系
|
||
* pid_t waitpid(int pid, int *status, int options)
|
||
*
|
||
* @param: sig: 信号编号
|
||
* =0 : 不发送信号,error check,用于检测一个进程或者进程组是否存在
|
||
*
|
||
* 返回值:成功返回0,失败返回-1并设置errno
|
||
* errno:
|
||
* EINVAL : 参数错误
|
||
* EPERM : 无权限发送信号
|
||
* ESRCH : 进程不存在
|
||
*/
|
||
int kill(pid_t pid, int sig);
|
||
|
||
/**
|
||
* raise - 发送信号给当前进程
|
||
* @sig: 信号编号
|
||
*
|
||
* 返回值:成功返回0,失败返回非零
|
||
*/
|
||
int raise(int sig);
|
||
kill(getpid(), SIGINT); // 等价
|
||
pthread_kill(pthread_self(), SIGINT); // 多线程等价
|
||
|
||
/**
|
||
* alarm - 设置闹钟
|
||
* @param: seconds: 闹钟时间,单位为秒
|
||
*
|
||
* @note:
|
||
* - 闹钟时间到时,会发送SIGALRM信号给当前进程。
|
||
* - 有的平台 sleep 是 alarm + pause 实现的
|
||
* 这时sleep和alarm在同一个程序中会冲突
|
||
* - 流量控制的基础
|
||
*
|
||
* @eg: 使用单一计时器,构造一组函数,实现任意数量的定时器
|
||
* 用到 alarm 或 setitimer
|
||
* TODO
|
||
*
|
||
* 返回值:成功返回0,失败返回非零
|
||
*/
|
||
unsigned int alarm(unsigned int seconds);
|
||
|
||
/**
|
||
* pause - 挂起进程
|
||
*
|
||
* @note: 挂起进程,直到收到信号或被唤醒
|
||
*/
|
||
int pause(void);
|
||
|
||
/**
|
||
* abort - 终止进程
|
||
*
|
||
* @note: 终止进程,立即退出,不执行清理函数,得到core文件
|
||
*/
|
||
void abort(void);
|
||
|
||
/**
|
||
* system - 执行系统命令
|
||
* @param: command: 命令字符串
|
||
* @note: 执行系统命令,阻塞当前进程,直到命令执行完毕
|
||
* ! 需要 block SIGCHLD 并且 ignore SIGINT SIGQUIT
|
||
* @return: 命令执行的返回值
|
||
*/
|
||
int system(const char *command);
|
||
|
||
sleep();
|
||
|
||
```
|
||
|
||
### 信号集
|
||
|
||
### 信号屏蔽字/`pending`的处理
|
||
|
||
### 扩展
|
||
|
||
```c
|
||
sigsuspend();
|
||
sigaction();
|
||
|
||
/**
|
||
* setitimer - 设置定时器
|
||
* @param: which: 定时器类型
|
||
* ITIMER_REAL : 实时定时器
|
||
* ITIMER_VIRTUAL : 虚拟定时器
|
||
* ITIMER_PROF : 进程计时器
|
||
* @param: new_value: 新的定时器值
|
||
* @param: old_value: 旧的定时器值
|
||
*
|
||
* @note: 原子赋值,比 alarm 更精细
|
||
*
|
||
* @return:成功返回0,失败返回-1并设置errno
|
||
*/
|
||
int setitimer(int which,
|
||
const struct itimerval *new_value,
|
||
struct itimerval *old_value);
|
||
|
||
// 在it_value之后第一次启动
|
||
// 后续以it_interval为间隔启动
|
||
struct itimerval {
|
||
struct timeval it_interval; /* next value */
|
||
struct timeval it_value; /* current value */
|
||
};
|
||
|
||
struct timeval {
|
||
time_t tv_sec; /* seconds */
|
||
suseconds_t tv_usec; /* microseconds */
|
||
};
|
||
|
||
/**
|
||
```
|
||
|
||
### 实时信号
|
||
|
||
## 线程(强烈异步)
|