Linux 多线程中执行fork的情况

一、普通多线程中执行fork的情况

1.多线程中没有执行fork的情况

代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
#include<semaphore.h>

void*fun(void* arg)
{
    for(int i=0;i<5;i++)
    {
        printf("fun线程(%d)在运行\n",getpid());//输出fun线程及其pid
        sleep(1);//每输出一次睡眠1秒
    }    
}

int main()
{
    pthread_t id;
    pthread_create(&id,NULL,fun,NULL);
    for(int i=0;i<5;i++)
    {
        printf("main线程(%d)在运行\n",getpid());//输出main线程及其pid
        sleep(1);//每输出一次睡眠1秒
    }
    pthread_join(id,NULL);

}

运行结果:

在这里插入图片描述
通过结果可以看出,两个线程同时执行,并且两个线程的pid是相同的。

2.多线程中在主线程中执行fork的情况

代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
#include<semaphore.h>

void*fun(void* arg)
{
    for(int i=0;i<5;i++)
    {
        printf("fun线程(%d)在运行\n",getpid());//输出fun线程及其pid
        sleep(1);//每输出一次睡眠1秒
    }    
}

int main()
{
    pthread_t id;
    pthread_create(&id,NULL,fun,NULL);

    fork();//产生一个子进程

    for(int i=0;i<5;i++)
    {
        printf("main线程(%d)在运行\n",getpid());//输出main线程及其pid
        sleep(1);//每输出一次睡眠1秒
    }
    pthread_join(id,NULL);

}

运行结果:

在这里插入图片描述

根据结果可以看出,在主线程中fork之后,父进程产生的子进程只有一条执行路径,子进程并没有产生新线程。所以产生一个结论,在多线程程序中,无论有多少个线程,fork之后产生的子进程中只有一条执行路径。

3.多线程中在子线程中执行fork的情况

代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
#include<semaphore.h>

void*fun(void* arg)
{
    fork();//产生一个子进程
    for(int i=0;i<5;i++)
    {
        printf("fun线程(%d)在运行\n",getpid());//输出fun线程及其pid
        sleep(1);//每输出一次睡眠1秒
    }    
}

int main()
{
    pthread_t id;
    pthread_create(&id,NULL,fun,NULL);

    for(int i=0;i<5;i++)
    {
        printf("main线程(%d)在运行\n",getpid());//输出main线程及其pid
        sleep(1);//每输出一次睡眠1秒
    }
    pthread_join(id,NULL);

}

运行结果:

在这里插入图片描述

从结果可以看出,无论fork在哪个线程被执行,fork最终产生的子进程就在哪个线程中执行。fork是复制进程的函数,从资源的角度来讲,父进程用的资源在fork之后都复制会给子进程。如果父进程运行了多个线程,那么在子进程只执行其中一个线程,这个线程就是fork所在的那个线程。无论是单线程还是多线程,fork之后产生的子进程只执行fork所在的那个线程。

4.总结

多线程程序fork之后产生的子进程只有一条执行路径,就是子进程所在的执行路径。

二、加锁的多线程执行fork的情况

1.创建一个互斥锁,在父进程中加锁,fork之后,子进程的情况

代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
#include<semaphore.h>
#include<sys/wait.h>

pthread_mutex_t mutex;//创建一个互斥锁变量

void*fun(void* arg)
{
    pthread_mutex_lock(&mutex);//加锁
    sleep(5);
    pthread_mutex_unlock(&mutex);//5秒后解锁
    printf("线程fun释放这个锁\n");
   
}

int main()
{

    pthread_mutex_init(&mutex,NULL);//初始化互斥锁
    pthread_t id;
    pthread_create(&id,NULL,fun,NULL);

    sleep(1);//睡眠1秒,等fun线程启动,并且已经加锁

    pid_t pid=fork();
    if(pid==0)
    {
        printf("子进程想要加锁\n");
        pthread_mutex_lock(&mutex);//子进程加锁
        printf("子进程加锁成功\n");
        pthread_mutex_unlock(&mutex);//子进程解锁

    }
    else
    {
        wait(NULL);//父进程等待子进程结束
    }
    
    printf("main程序结束\n");
}

代码思路:首先创建一个全局的互斥锁,然后在主线程中创建了一个线程fun,在fun中先执行加锁的操作,然后等待5秒,执行解锁的操作,然后线程fun就结束了,在当主线程中创建完新线程fun之后,等待1秒,等待1秒是为了在1秒钟以后在线程fun中已经加锁了,然后执行fork产生一个子进程,在子进程中执行加锁,如果可以加锁成功就会输出加锁成功,父进程在等待子进程结束,如果子进程可以成功执行加锁解锁操作顺利结束,wait就可以返回了,否则wait就会阻塞,等待子进程结束。

运行结果:

在这里插入图片描述

根据结果可以看先输出子进程想要加锁,但是没有打印出子进程加锁成功,此时线程fun已经释放锁了,但是子进程依然没有加锁成功,说明子进程在加锁的地方阻塞住了,又由于主线程在wait等待子进程结束,因为子进程没有结束,所以主线程在wait的地方也阻塞住了,导致当前的程序一直没有执行完成。这说明子进程加锁没有成功。而一般加锁不成功的原因是因为已经加锁了,这种情况下才会在加锁的时候发生阻塞。所以,有可能是子进程已经加锁了,所以再次加锁的时候被阻塞住了。

子进程加锁没有成功的详细原因:

父进程加锁之后,执行fork产生子进程,这个锁也会被复制,所以父进程和子进程各自又都锁,而fork的时候锁的状态是加锁状态,所以fork产生的子进程也是处于加锁状态。但是要注意父进程和子进程的锁各自是各自的,是不同的两个锁,相互之间不影响,所以当父进程的线程fun释放锁之后,子进程中锁的状态不会改变,还是加锁状态。所以如果父进程中有互斥锁,那么父进程中锁的状态是什么,在fork之后产生的子进程中的锁的状态就是什么。

2.pthread_atfork()

在这里插入图片描述

参数解释:
3个参数都是函数指针
第1个参数:指向的函数是在fork之前执行
第2个参数:指向的函数是在fork之后父进程中执行
第2个参数:指向的函数是在fork之后子进程中执行
返回值为0表示函数执行成功。值得注意的是,无论函数定义在哪里,只有在下一个fork()执行前,该函数才被执行。

在第一个参数指向的函数中加锁,在第二个参数指向的函数中解锁,并释放锁,在第三个参数指向的函数中也释放锁。

代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
#include<semaphore.h>
#include<sys/wait.h>

pthread_mutex_t mutex;//创建一个互斥锁变量

void parent_fun(void)//该函数在fork之前执行
{
    pthread_mutex_lock(&mutex);//加锁
}

void child_fun(void)//该函数在fork之后执行
{
    pthread_mutex_unlock(&mutex);//解锁
}

void*fun(void* arg)
{
    pthread_mutex_lock(&mutex);//加锁
    sleep(5);
    pthread_mutex_unlock(&mutex);//5秒后解锁
    printf("线程fun释放这个锁\n");
   
}

int main()
{

    pthread_mutex_init(&mutex,NULL);//初始化互斥锁

    //在fork之前执行parent_fun,
    //fork之后在父进程中执行第二个参数child_fun,在子进程中执行第三个参数child_fun
    pthread_atfork(parent_fun,child_fun,child_fun);
    
    pthread_t id;
    pthread_create(&id,NULL,fun,NULL);

    sleep(1);//睡眠1秒,等fun线程启动,并且已经加锁

    pid_t pid=fork();
    if(pid==0)
    {
        printf("子进程想要加锁\n");
        pthread_mutex_lock(&mutex);
        printf("子进程加锁成功\n");
        pthread_mutex_unlock(&mutex);

    }
    else
    {
        wait(NULL);
    }
    
    printf("main程序结束\n");
}

运行结果:

在这里插入图片描述

3.总结

父进程中有互斥锁,那么父进程中锁的状态是什么,在fork之后产生的子进程中的锁的状态就是什么。如果在多线程程序中父进程中加了锁,在fork之后,子进程中要使用这个锁,那么就需要用到pthread_atfork(),pthread_atfork()的实现思路就是看没有进程用锁的时候,在进行fork去产生子进程,以确保父子进程中锁的状态是清晰的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值