main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的 时候,因 为硬件中断使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换 到sighandler函 数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入操作的 两步都做完之后从 sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续 往下执行,先前做第一步 之后被打断,现在继续做完第二步。结果是,main函数和sighandler先后 向链表中插入两个节点,而最后只 有一个节点真正插入链表中了。
像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称 为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为 不可重入函数,反之, 如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数。
如果一个函数符合以下条件之一则是不可重入的:
调用了malloc或free,因为malloc也是用全局链表来管理堆的。
调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构
我们先来看看程序:
gcc -o $@ $^ -O3 -std=c++11
编译时加上 -O3选项进行优化(在CPU寄存器中优化)。
int quit = 0;
void myhandler(int signo)
{
cout << "已收到信号 :" << signo <
由于在main中只进行比较,并不改变quit,从内存中取结果的过程被省去了。
要保持内存可见性 ,则可以通过添加volatil关键字解决,每次使用数据都从内存中取,再放回去。
volatile int quit = 0;
其实,子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自 定义SIGCHLD信号 的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程 终止时会通知父进程,父进程在信号处理 函数中调用wait清理子进程即可。
如果不对SIGCHLD信号处理函数做处理,结果如下:
int main()
{
// 显示的设置对SIGCHLD进行忽略
// signal(SIGCHLD, SIG_IGN);
printf("我是父进程, %d, ppid: %d\n", getpid(), getppid());
pid_t id = fork();
if (id == 0)
{
printf("我是子进程, %d, ppid: %d,我要退出啦\n", getpid(), getppid());
Count(10);
exit(1);
}
while (1)
sleep(1);
return 0;
}
子进程退出后变成僵尸代码,等待父进程回收。
我们利用子进程在终止时会给父进程发SIGCHLD信号,实现自动回收。
但有可能同时有许多子进程在同一时刻退出,会出现信号丢失,所以要在信号处理函数中一次非阻塞回收所有僵尸进程。
void handler(int signo)
{
//1. 我有非常多的子进程,在同一个时刻退出了
//waitpid(-1) -> while(1)
//2. 我有非常多的子进程,在同一个时刻只有一部分退出了
while(1)
{
pid_t ret = waitpid(-1, NULL, WNOHANG);//非阻塞式等待
if(ret == 0) break;
}
}
void Count(int cnt)
{
while (cnt)
{
printf("cnt: %2d\r", cnt);
fflush(stdout);
cnt--;
sleep(1);
}
printf("\n");
}
int main()
{
// 显示的设置对SIGCHLD进行忽略
// signal(SIGCHLD, SIG_IGN);
signal(SIGCHLD, handler);
printf("我是父进程, %d, ppid: %d\n", getpid(), getppid());
pid_t id = fork();
if (id == 0)
{
printf("我是子进程, %d, ppid: %d,我要退出啦\n", getpid(), getppid());
Count(5);
exit(1);
}
while (1)
sleep(1);
return 0;
}
实现自动回收。
事实上,由于UNIX 的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调 用sigaction将SIGCHLD的处理动作 置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不 会产生僵尸进程,也不会通知父进程。系统默认的忽 略动作和用户用sigaction函数自定义的忽略 通常是没有区别的,但这是一个特例。此方法对于Linux可用,但不保证 在其它UNIX系统上都可 用。
signal(SIGCHLD, SIG_IGN);
和我们自己写的信号处理函数效果相同。