linux 提供了一个称之为 pthread_atfork 的函数,它允许我们事先注册三个回调函数。一旦 fork 被调用,这三个回调函数会被执行。
1. 函数原型
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));
上面三个回调函数,分别为 prepare 函数,parent 函数,child 函数。这些函数的调用时机如下:
- prepare 函数是在 fork 还会产生子进程时调用
- parent 和 child 是在产生子进程后调用
- parent 在父进程中调用,child 在子进程中调用
用伪代码来说明调用时机:
prepare();
pid = fork();
if (pid > 0) {
parent();
}
else if (pid == 0) {
child();
}
只不过 atfork 注册的这几个函数调用时间是在进入 fork 后,返回 fork 前而已。但是它们做的事情,几乎等同于上面的伪代码。
2. 利用 atfork 解决 fork 死锁
程序 atfork_lock 是解决上一篇文章中的 fork 死锁问题,你会发现,它的解决方案其实和上一文中的几乎差不多。只是,更推荐用 atfork 这种方式。
2.1 代码
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#define PERR(err, msg) do { errno = err; perror(msg); exit(-1); } while(0)
int total = 0;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void prepare() {
int err;
puts("preparing locks...");
err = pthread_mutex_lock(&lock);
if (err != 0) PERR(err, "prepare lock failed");
}
void parent() {
int err;
puts("parent unlocking locks...");
err = pthread_mutex_unlock(&lock);
if (err != 0) PERR(err, "parent unlock failed");
}
void child() {
int err;
puts("child unlocking locks...");
err = pthread_mutex_unlock(&lock);
if (err != 0) PERR(err, "child unlock failed");
}
void* fun(void* arg) {
while(1) {
pthread_mutex_lock(&lock);
total++;
puts("fun: total++");
sleep(5);
pthread_mutex_unlock(&lock);
sleep(1);
}
return NULL;
}
int main() {
int err;
pid_t pid;
pthread_t tid;
pthread_create(&tid, NULL, fun, NULL);
err = pthread_atfork(prepare, parent, child);
if (err != 0) PERR(err, "atfork");
sleep(1);
puts("parent about to fork ...");
pid = fork();
if (pid < 0) PERR(errno, "fork");
else if (pid == 0) {
// child
int status;
while(1) {
pthread_mutex_lock(&lock);
total++;
puts("child: total++");
sleep(2);
pthread_mutex_unlock(&lock);
sleep(1);
}
exit(0);
}
pthread_join(tid, NULL);
}
2.2 编译和运行
- 编译运行
$ gcc atfork_lock.c -o atfork_lock -lpthread
$ ./atfork
- 运行结果
3. 总结
- 知道 atfork 注册的三个回调函数调用时机