fork多线程中需要注意的问题

单线程场景

对于fork系统调用,我们知道是linux下创建子进程的一种方式。fork调用一次,对于程序看来,是“返回两次”。这里其实理解为fork调用中,已经创建出了子进程,父子进程分别分从fork调用中返回。父进程需要知道子进程的进程ID,所以返回值大于0的是父进程,而子进程返回0即可,子进程可以通过getpid获取自身进程ID和getppid获取父进程ID。

多线程场景

对于多线程场景中,例如一个10个线程的进程中。如果某个线程调用了fork,新的子进程中会有多少个线程?请思考30秒。
正确答案是:直接man fork即可看到。

Note the following further points:
The child process is created with a single thread—the one that called fork(). The entire virtual address space of the parent is replicated in the child, including the states of mutexes, condition variables, and other pthreads objects; the use of pthread_atfork(3) may be helpful for dealing with problems that this can cause.

结论:只有当前线程会被复制到子线程中。

为什么fork只复制当前线程

试想,如果多个线程都被复制到了子进程中,除了当前线程能通过fork返回值判断,其他线程怎么感知到自己被复制到了子线程呢?从逻辑上就比较容易推断出,这种多线程同时复制到子进制的fork方案是不合理的。

锁资源

锁资源例如mutex,同样也是会被复制到子进程中。那么就会出现一种情况,当前线程的mutex的状态被复现到了子进程中。如果mutext是被加锁状态,则在子进程如果尝试对mutext进行加锁的时候,会导致子锁的产生。如下代码所示:

#include 
#include 
#include 
#include 
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void *fun(void *arg)
{
    printf("pid = %d begin doit ...\n", static_cast<int>(getpid()));
    pthread_mutex_lock(&mutex);
    struct timespec ts = {2, 0};
    nanosleep(&ts, NULL);
    pthread_mutex_unlock(&mutex);
    printf("pid = %d end doit ...\n", static_cast<int>(getpid()));
    return NULL;
}
int main(void)
{
    printf("pid = %d Entering main ...\n", static_cast<int>(getpid()));
    pthread_t tid;
    pthread_create(&tid, NULL, fun, NULL);
    struct timespec ts = {1, 0};
    nanosleep(&ts, NULL);
    if (fork() == 0)
    {
        fun(NULL);
    }
    pthread_join(tid, NULL);
    printf("pid = %d Exiting main ...\n", static_cast<int>(getpid()));
    return 0;
}

解决方案

通过int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void)),这个系统调用解决这类问题。
主要流程说明:
1、在prepare函数中去获取父进程中所有的锁资源。
2、在parent函数中去释放所有的锁资源。
3、在child函数中去释放所有的锁资源。

这样即可保证,父进程的锁不受到影响,子进程的初始化的锁状态是未锁定的。
参考以下代码:

#include 
#include 
#include 
#include 
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void *fun(void *arg)
{
    printf("pid = %d begin doit ...\n", static_cast<int>(getpid()));
    pthread_mutex_lock(&mutex);
    struct timespec ts = {2, 0}; 
    nanosleep(&ts, NULL);
    pthread_mutex_unlock(&mutex);
    printf("pid = %d end doit ...\n", static_cast<int>(getpid()));
    return NULL;
}

void prepare() {
    printf("begin prepare\n"); 
    pthread_mutex_lock(&mutex);
    printf("after prepare\n");
}

void parent() {
    printf("begin parent\n");
    pthread_mutex_unlock(&mutex);
    printf("after parent\n");

}

void child() {
    printf("begin child\n"); 
    pthread_mutex_unlock(&mutex);
    printf("after child\n");
}

int main(void)
{
    pthread_atfork(prepare, parent, child);
    printf("pid = %d Entering main ...\n", static_cast<int>(getpid()));
    pthread_t tid;
    pthread_create(&tid, NULL, fun, NULL);
    
    if (fork() == 0)
    {   
        printf("child process pid[%d]\n", getpid());
        fun(NULL);
    }   
    pthread_join(tid, NULL);
    printf("pid = %d Exiting main ...\n", static_cast<int>(getpid()));
    return 0;
}

你可能感兴趣的:(C/C++,linux)