【Linux】多线程中使用fork()

news/2024/7/1 10:06:29

(最核心的东西我在下面用红色字体标出来了,理解了那块,这些东西都是就理解了!)

在本篇文章开始之前,需要大家先了解线程和进程,这位大哥讲的言简意赅:进程和线程的主要区别(总结)_kuangsonghan的博客-CSDN博客_进程与线程的区别

如标题所示,如果在多线程中的某一个线程中使用fork函数之后,fork产生的子进程会不会复制父进程中的线程呢?

答案是不会的,fork的时候只复制当前线程到子进程,下面是fork的描述:

fork(2): create child process - Linux man page

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.
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.

下面我们来看一下下面这个例子:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/wait.h>pthread_mutex_t mutex; //互斥锁的创建void * fun(void * arg)
{pthread_mutex_lock(&mutex);printf("fun lock \n");sleep(5);pthread_mutex_unlock(&mutex);printf("fun unlock \n");
} int main()
{pthread_mutex_init(&mutex,NULL);pthread_t id;pthread_create(&id,NULL,fun,NULL);sleep(1);pid_t pid = fork();if(pid == -1){exit(0);}if(pid == 0){printf("child will lock \n");pthread_mutex_lock(&mutex);printf("chile lock \n");pthread_mutex_unlock(&mutex);printf("child umlock \n");exit(0);}wait(NULL);printf("main end \n");exit(0);
}

上述代码中整体的逻辑  我来给大家理一下,先开始是主线程创建了子线程,然后子线程中啥也不干,加锁----输出fun lock------睡眠五秒钟------解锁-----输出fun unlock

等不到子线程执行完,主线程又执行了fork命令,将主线程复制一份到子进程中,这会的主线程也充当了子进程的父进程,因为所有线程共享一把锁,这会子线程中的锁还在枷锁状态中,所以子进程中也就被fork将锁的加锁状态锁给复制到了子进程中,

这时候到了子进程中的时候,子进程首先打印出child will lock 然后准备加锁的时候发现,锁是夹着的状态,所以就一直会阻塞在子进程中加锁的那一步,ok,整体梳理了一遍,我们来看上述代码的运行结果:

 和我上述理的思路一毛一样,最后程序一直阻塞到了子进程中加锁那步,那么这种情况怎么解决呢?

首先我们在fork的时候,我们不清楚锁的状态,所以才会造成上面的问题出现,所以我们必须在fork的时候就清楚锁的状态:

其实在Linux中人家已经帮我们写好了,他就是:pthread_atfork

atfork() --同步父子进程 pthread_mutex_lock加解锁问题_Turbyun的博客-CSDN博客

pthread_atfork(void (*prepare)(void),void (*parent)(void), void(*child)(void))

 该函数中的三个参数都是指向函数的指针

第一个参数是在调用fork之前该调用的函数

第二个参数是在调用fork之后,父进程该调用的函数

第三个参数是在调用fork之后,子进程该调用的函数

ok   上述参数也交代清楚了   那么在我们上述的代码中这三个函数分别应该怎么写那?

在调用fork之前应该调用的函数:对互斥锁的加锁

void before_fork()
{pthread_mutex_lock(&mutex);
}

在调用fork之后应该调用的函数:对互斥锁的解锁

void after_fork()
{pthread_mutex_unlock(&mutex);
}

这时候 我们已经清楚锁的状态了,这种也就是清楚锁的状态的时候,fork才会进程执行,也就是推迟fork执行的时间,那么加上代码之后我们来看总代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/wait.h>pthread_mutex_t mutex; //互斥锁的创建void before_fork()
{pthread_mutex_lock(&mutex);
}void after_fork()
{pthread_mutex_unlock(&mutex);
}void * fun(void * arg)
{pthread_mutex_lock(&mutex);printf("fun lock \n");sleep(5);pthread_mutex_unlock(&mutex);printf("fun unlock \n");
} int main()
{pthread_mutex_init(&mutex,NULL);pthread_t id;pthread_create(&id,NULL,fun,NULL);sleep(1);pthread_atfork(before_fork,after_fork,after_fork);  //这行代码只要在fork之前就行pid_t pid = fork();if(pid == -1){exit(0);}if(pid == 0){printf("child will lock \n");pthread_mutex_lock(&mutex);printf("chile lock \n");pthread_mutex_unlock(&mutex);printf("child umlock \n");exit(0);}wait(NULL);printf("main end \n");exit(0);
}

然后执行的时候,fork会等待子线程解锁之后才执行:

一般来说  真实的代码中不会让子线程中sleep(5)然后再执行fork   这只是为了让我们能看的更清楚,所以才加的,真实的是子线程加锁解锁然后fork,是很快的,肉眼根本察觉不到的!

 

“每一个不曾起舞的日子,都是对生命的辜负!”

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pgtn.cn/news/15607.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

Linux网络编程--TCP中的三次握手和四次挥手

服务器编程和客户端编程的大致流程如下&#xff1a; 三次握手是在客户端中的connect中完成的&#xff0c;具体如下&#xff1a; 那么上述说到的SYN ACK这些是什么东西呢&#xff1f; 上述的截图取自《Linux高性能服务器编程》电子版的截图&#xff01; 根据书中所提到的在…

TCP协议的服务器与客户端的程序设计(代码注释超详细)

在上篇博客中讲到了三次握手和四次挥手: Linux网络编程--TCP中的三次握手和四次挥手_神厨小福贵&#xff01;的博客-CSDN博客服务器编程和客户端编程的大致流程如下&#xff1a;三次握手是在客户端中的connect中完成的&#xff0c;具体如下&#xff1a;那么上述说到的SYN ACK这…

内联函数inline

结论在文章末尾处&#xff01; 在C中&#xff0c;为了解决一些频繁调用的小函数大量消耗栈空间&#xff08;栈内存&#xff09;的问题&#xff0c;特别的引入了inline修饰符&#xff0c;表示为内联函数。 内联函数的处理方式是在函数的调用点直接代码展开。在计算机系统下&am…

TCP的协议特点(对于《Linux高性能服务器编程》的部分摘录以及自己的部分见解)

一.TCP协议特点为面向连接、字节流和可靠传输 面向连接&#xff1a;使用TCP协议通信的双方必须先建立连接&#xff0c;然后才能开始数据的读写。双方都必须为该连接分配必要的内核资源&#xff0c;以管理连接的状态和连接上数据的传输。TCP连接是全双工的&#xff0c;即双方的…

【SVN多用户开发】代码冲突解决办法

SVN是一款集中式的代码存储工具&#xff0c;可以帮助多个用户协同开发同一应用程序。 但是SVN不能完全代替人工操作&#xff0c;有时也需要程序员自己进行沟通确认有效的代码。 下面就简单的看一下&#xff0c;常见的代码冲突以及解决方法。 总结起来&#xff0c;无非是&#x…

UDP通信协议详解

中文名 用户数据报协议英文名 User Datagram Protocol 基础 IP数据包服务上增加一点功能 类别 传输层协议 特点 无连接、不可靠、快速传输 用途发送IP数据包 如右图所所示为udp协议的基本信息 上图就是UDP的数据报服务,sendto两次发送的是单独的两个个体,接收端…

简单的HTTP服务器程序的编写

HTTP 协议一般指 HTTP&#xff08;超文本传输协议&#xff09;。 超文本传输协议&#xff08;英语&#xff1a;HyperText Transfer Protocol&#xff0c;缩写&#xff1a;HTTP&#xff09;是一种用于分布式、协作式和超媒体信息系统的应用层协议&#xff0c;是因特网上应用最为…

【C++】何时需要自定义拷贝构造函数和赋值符

先来说结论&#xff1a;当类中有指针类型成员变量的时候&#xff0c;一定要自定义拷贝构造和赋值运算符 原因&#xff1a;当我们在有指针类成员变量的时候&#xff0c;还是用默认拷贝构造函数(拷贝构造函数执行的时候会调用赋值符)&#xff0c;默认赋值为浅拷贝&#xff0c;会…