Linux 多线程中执行fork的情况

news/2024/11/29 5:41:46/

一、普通多线程中执行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线程及其pidsleep(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线程及其pidsleep(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线程及其pidsleep(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线程及其pidsleep(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线程及其pidsleep(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线程及其pidsleep(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_funpthread_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去产生子进程,以确保父子进程中锁的状态是清晰的。


http://www.ppmy.cn/news/1051590.html

相关文章

TLIN1021A-Q1 故障保护 LIN 收发器

​​​​​​目录 ​​1 特性 2 应用 3 说明 5描述(续) 6引脚配置和功能 7规范

Matlab论文插图绘制模板第108期—特征渲染的标签散点图

在之前的文章中&#xff0c;分享了Matlab标签散点图的绘制模板&#xff1a; 进一步&#xff0c;再来分享一下特征渲染的标签散点图的绘制模板&#xff0c;以便再添加一个维度的信息。 先来看一下成品效果&#xff1a; 特别提示&#xff1a;本期内容『数据代码』已上传资源群中…

LInux之例行工作

目录 场景 单一执行例行任务 --- at&#xff08;一次性&#xff09; 安装 命令详解 语法格式 参数及作用 时间格式 案例 at命令执行过程分析 循环执行的例行性任务--crontab&#xff08;周期性&#xff09; crontd服务安装 linux 任务调度的工分类 crontab工作过程…

Three.js 实现模型材质局部辉光(发光,光晕)效果和解决辉光影响场景背景图显示的问题

1.Three.js 实现模型材质局部辉光&#xff08;发光&#xff0c;光晕&#xff09;效果 2.解决辉光效果影响场景背景图显示的问题 相关API的使用&#xff1a; 1. EffectComposer&#xff08;渲染后处理的通用框架&#xff0c;用于将多个渲染通道&#xff08;pass&#xff09;组…

vue 使用nvm控制node 版本,随意切换 node 版本

1.nvm 下载安装 https://github.com/coreybutler/nvm-windows/releases 找自己版本 1.安装版本 nvm list available // 查看所有node 版本 nvm install 版本号 // 安装指定版本号2.nvm 列表展示 nvm list //展示所有版本号3.nvm 切换环境 nvm use 版本号 // 切换版本4.…

智能算法挑战赛决赛题目——初中组

题目 1. 判断是否存在重复的子序列 从 m 个字符中选取字符&#xff0c;生成 n 个符号的序列&#xff0c;使得其中没有 2 个相邻的子序列相同。如从 1&#xff0c;2&#xff0c;3&#xff0c;生成长度为 5 的序列&#xff0c;序列“12321”是合格的&#xff0c;而“12323”和“…

群狼调研(长沙政策第三方评估)| 社情民意调查的内容

本文由群狼调研(长沙社会舆情调查)出品&#xff0c;欢迎转载&#xff0c;请注明出处。社情民意调查旨在捕捉公众对各种社会问题的态度、意见和看法&#xff0c;社情民意调查的内容通常包括以下几个方面&#xff1a; 1. 社会热点问题&#xff1a;针对当前社会热点问题进行调查&…

创新科技引领智能商场:数字化体验助您畅享购物乐趣

随着科技的不断发展&#xff0c;智能商场正成为人们日常购物的新选择。以数字化为核心&#xff0c;智能商场通过集成软件系统与智能化设备&#xff0c;为购物者提供全新的购物体验。本文将探讨智能商场的软件系统架构、市场情况&#xff0c;并揭示其在软件产品研发和销售上带来…