文章目录
一、全局变量异步IO可能会造成什么问题?
父子进程分别对累加:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
// 父子进程分别对n进行累加
// 每隔1秒,相互发送信号通知累加
int n = 0, flag = 0;
void sys_err(char *str)
{
perror(str);
exit(EXIT_FAILURE);
}
void deal_sig_child(int signo)
{
printf("I am child %d\t%d\n", getpid(), n);
n += 2;
flag = 1; // 计数完成
//sleep(1);
}
void deal_sig_parent(int num)
{
printf("I am parent %d\t%d\n", getpid(), n);
n += 2;
flag = 1; // 计数完成
//sleep(1);
}
int main()
{
pid_t pid;
struct sigaction act;
if ((pid = fork()) < 0)
{
sys_err("fork");
}
else if (pid > 0)
{
n = 1;
sleep(1); // 保证子进程注册信号的动作能完成
act.sa_handler = deal_sig_parent;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGUSR2, &act, NULL); // 注册SIGUSR2的捕捉函数
// 父进程先开始累加
deal_sig_parent(0);
while(1)
{
// wait for signal
if (flag == 1)
{
kill(pid, SIGUSR1);
// ----------------这里可能失去CPU
flag = 0;
}
}
}
else if (pid == 0)
{
n = 2;
act.sa_handler = deal_sig_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGUSR1, &act, NULL); // 注册SIGUSR1的捕捉函数
while(1)
{
// wait for signal
if (flag == 1)
{
kill(getppid(), SIGUSR2); // 给父进程发送SIGUSR2
flag = 0;
}
}
}
return 0;
}
SIGUSR1
: 10,用户定义的信号。即程序员可以在程序中定义并使用该信号。默认动作为终止进程。
SIGUSR2
: 12,另一个用户定义的信号。即程序员可以在程序中定义并使用该信号。默认动作为终止进程。
如果注释掉代码中的两处sleep(1),会出现一个问题:n累加一会后,不会再累加,程序进入循环等待。原因如下:
- 父进程调用kill给子进程发送信号,通知开始计数。
- 父进程在执行flag=0前可能会失去CPU,等待CPU。
- 而子进程计数完成,会发送信号给父进程。
- 父进程获得CPU后,先调用信号处理函数,将 flag = 1;
- 父进程处理完信号后,恢复到断点继续处理程序,执行flag = 0,flag 的值被覆盖。
- 此时子进程不再给父进程发送信号,父进程也不能给子进程发送信号,程序陷入死循环。
解决方法:
- 加锁;
- 不使用全局变量,使信号处理函数成为可重入函数。
二、什么是可重入函数?
一个函数在被调用执行期间(尚未调用结束),由于某种时序又被重复调用,称之为
重入
。根据函数实现的方法分为可重入函数
和不可重入函数
两种。
1、可重入函数
int main()
{
func(a) {
...
func(a);
...
}
}
int main()
{
func(a);
func(a);
}
// 如果上面两个main函数执行的结果一致,则称func(int a)为可重入函数。
递归调用与连续调用多次,结果一致,就是可重入函数。
2、不可重入函数
显然,insert函数是不可重入函数,重复调用,会导致意外的结果。究其原因,是该函数内部实现使用了全局变量head
。
3、注意事项
定义可重入函数,函数内不能含有全局变量及static变量,不能使用malloc、free。
信号捕捉函数应该设计为可重入函数。
信号处理程序可以调用的可重入函数可以参考
man 7 signal
。没有包含在上述列表中的函数大多是不可重入函数。其原因为:
a) 使用了静态数据结构
b) 调用了malloc或free
c) 是标准IO函数