linux线程函数 pthread_atfork 的深入理解
在进行linux系统里开发时,经常会调用linux的系统函数fork来产生一个子进程,如果父子进程都没有用到pthread线程相关函数,则就不存在需要理解pthread_atfork的函数的必要。问题是有时候既要考虑多线程,又要考虑多进程,这个时候就要仔细理解pthread_atfork这个函数的作用了。 在父进程调用fork函数派生子进程的时候,如果父进程创建了pthread的互斥锁(..
在进行linux系统里开发时,经常会调用linux的系统函数fork来产生一个子进程,如果父子进程都没有用到pthread线程相关函数,则就不存在需要理解pthread_atfork的函数的必要。问题是有时候既要考虑多线程,又要考虑多进程,这个时候就要仔细理解pthread_atfork这个函数的作用了。
在父进程调用fork函数派生子进程的时候,如果父进程创建了pthread的互斥锁(pthread_mutex_t)对象,那么子进程将自动继承父进程中互斥锁对象,并且互斥锁的状态也会被子进程继承下来:如果父进程中已经加锁的互斥锁在子进程中也是被锁住的,如果在父进程中未加锁的互斥锁在子进程中也是未加锁的。在父进程调用fork之前所创建的pthread_mutex_t对象会在子进程中继续有效,而pthread_mutex_t对象通常是全局对象,会在父进程的任意线程中被操作(加锁或者解锁),这样就无法通过简单的方法让子进程明确知道被继承的 pthread_mutex_t对象到底有没有处于加锁状态。因此 pthread线程库就有了 pthread_atfork 这个函数,该函数的原型声明如下:
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));
该函数通过3个不同阶段的回调函数来处理互斥锁状态。参数如下:
prepare:将在fork调用创建出子进程之前被执行,它可以给父进程中的互斥锁对象明明确确上锁。这个函数是在父进程的上下文中执行的,正常使用时,我们应该在此回调函数调用 pthread_mutex_lock 来给互斥锁明明确确加锁,这个时候如果父进程中的某个线程已经调用pthread_mutex_lock给互斥锁加上了锁,则在此回调中调用 pthread_mutex_lock 将迫使父进程中调用fork的线程处于阻塞状态,直到prepare能给互斥锁对象加锁为止。
parent: 是在fork调用创建出子进程之后,而fork返回之前执行,在父进程上下文中被执行。它的作用是释放所有在prepare函数中被明明确确锁住的互斥锁。
child: 是在fork返回之前,在子进程上下文中被执行。和parent处理函数一样,child函数也是用于释放所有在prepare函数中被明明确确锁住的互斥锁。
函数成功返回0, 错误返回错误码。
通过这个函数可以确保子进程继承pthread_mutex_t对象处在未加锁状态。该函数的正常的用法如下:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#define ERROR(err, msg) do { errno = err; perror(msg); exit(-1); } while(0)
int count = 0;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void prepare() {
int err;
printf("prepare: pthread_mutex_lock ...\n");
err = pthread_mutex_lock(&lock);
if (err != 0) ERROR(err, "prepare: pthread_mutex_lock failed");
printf("prepare: lock start...\n");
}
void parent() {
int err;
printf("parent: pthread_mutex_unlock ...\n");
err = pthread_mutex_unlock(&lock);
if (err != 0) ERROR(err, "parent: pthread_mutex_unlock");
}
void child() {
int err;
printf("child: pthread_mutex_unlock ...\n");
err = pthread_mutex_unlock(&lock);
if (err != 0) ERROR(err, "child: pthread_mutex_unlock");
}
void* thread_proc(void* arg) {
while(1) {
pthread_mutex_lock(&lock);
count++;
printf("parent thread: count:%d\n",count);
sleep(10);
pthread_mutex_unlock(&lock);
sleep(1);
}
return NULL;
}
int main(int argc,char * argv[])
{
int err;
pid_t pid;
pthread_t tid;
pthread_create(&tid, NULL, thread_proc, NULL);
err = pthread_atfork(prepare, parent, child);
if (err != 0) ERROR(err, "atfork");
sleep(1);
printf("parent is about to fork ...\n");
pid = fork();
if (pid < 0) ERROR(errno, "fork");
else if (pid == 0) {
// child process
int status;
printf("child running\n");
while(1) {
pthread_mutex_lock(&lock);
count ++;
printf("child: count:%d\n",count);
sleep(2);
pthread_mutex_unlock(&lock);
sleep(1);
}
exit(0);
}
pthread_join(tid, NULL);
return 0;
}
编译方法(低版本GCC):
gcc -O2 -o atfork -lpthread atfork.c
如果GCC版本比较低用-lpthread来链接pthread库,高版本的gcc例如gcc 7.40通过-lpthread编译时会出现如下错误:
undefined reference to `pthread_atfork'
将 -lpthread 改成 -pthread 即可。
高版本GCC编译链接:
gcc -O2 -o atfork -pthread atfork.c
运行 atfork 发现 main函数会在prepare回调中等待:prepare: pthread_mutex_lock,直到获得互斥锁以后再执行子进程创建。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)