博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
在多线程程序里面fork
阅读量:6420 次
发布时间:2019-06-23

本文共 2888 字,大约阅读时间需要 9 分钟。

在多线程程序里面fork?没错,尽管这是一种很奇怪、以至于几乎不会有人使用的玩法,并且存在死锁等不确定因素。不过讨论讨论倒也挺有意思。

进程A,创建了3个线程。
$ ls /proc/A/task/ | wc -l
3
这时候,A调用fork,创建了进程B。那么B有几个线程呢?
$ ls /proc/B/task/ | wc -l
1
从《》可以看出,linux所谓的“进程”和“线程”,本质上都是同样的“执行体”。A进程是一个执行体,而fork是对A的复制,所以它调用fork创建出来的B进程也只是一个执行体。
再来看看内存。
$ cat /proc/A/maps
...
00501000-00522000 rwxp 00501000 00:00 0
40000000-40001000 ---p 40000000 00:00 0
40001000-40a01000 rw-p 40001000 00:00 0
40a01000-40a02000 ---p 40a01000 00:00 0
40a02000-41402000 rw-p 40a02000 00:00 0
41402000-41403000 ---p 41402000 00:00 0
41403000-41e03000 rw-p 41403000 00:00 0
...
我们可以看到进程A的内存分配情况。注意,里面包含了3个线程的栈空间。
fork得到的进程B呢?
$ cat /proc/B/maps
...
00501000-00522000 rwxp 00501000 00:00 0
40000000-40001000 ---p 40000000 00:00 0
40001000-40a01000 rw-p 40001000 00:00 0
40a01000-40a02000 ---p 40a01000 00:00 0
40a02000-41402000 rw-p 40a02000 00:00 0
41402000-41403000 ---p 41402000 00:00 0
41403000-41e03000 rw-p 41403000 00:00 0
...
跟A一模一样,尽管线程的“执行体”没有被复制,但是栈空间却都被复制了。
因为fork会对进程A的资源进行完全的复制,而A上面的3个线程的栈空间都是在A的内存空间上的,所以栈都被复制了。光有栈而没有执行体,那么,在进程B上面这些栈空间是不是就成了垃圾了呢?的确应该是这样的。
那么,我们在进程B上面也创建3个线程看看呢?这样,B上面是不是将存在6个线程栈呢?
$ cat /proc/B/maps
...
00501000-00522000 rwxp 00501000 00:00 0
40000000-40001000 ---p 40000000 00:00 0
40001000-40a01000 rw-p 40001000 00:00 0
40a01000-40a02000 ---p 40a01000 00:00 0
40a02000-41402000 rw-p 40a02000 00:00 0
41402000-41403000 ---p 41402000 00:00 0
41403000-41e03000 rw-p 41403000 00:00 0
...
内存分配还是一样的,原本以为成为了垃圾的栈空间又被重新利用上了。
这是为什么呢?首先,我们使用的线程库(NPTL)是glibc的一部分;而我们调用的fork也是被glibc封装过的系统调用。glibc知道你要fork了,也知道fork之后会在进程B里面留下一堆垃圾(进程A中的线程栈),于是就在进程B中将这些垃圾管理了起来。当进程B需要创建线程、需要分配线程栈时,就能把这些垃圾重复利用。(具体可以从glibc的源码中找到答案,不过glibc源码可读性实在太差了点,就不列举了。)
我们再把fork函数换一换,不要使用glibc封装过的,直接使用系统调用(调用syscall(__NR_fork))。
$ cat /proc/B/maps
...
00501000-00522000 rwxp 00501000 00:00 0
40000000-40001000 ---p 40000000 00:00 0
40001000-40a01000 rw-p 40001000 00:00 0
40a01000-40a02000 ---p 40a01000 00:00 0
40a02000-41402000 rw-p 40a02000 00:00 0
41402000-41403000 ---p 41402000 00:00 0
41403000-41e03000 rw-p 41403000 00:00 0
41e03000-41e04000 ---p 41e03000 00:00 0
41e04000-42804000 rw-p 41e04000 00:00 0
42804000-42805000 ---p 42804000 00:00 0
42805000-43205000 rw-p 42805000 00:00 0
43205000-43206000 ---p 43205000 00:00 0
43206000-43c06000 rw-p 43206000 00:00 0
...
果然,fork不经过glibc,glibc就不知道可以将进程A中的那些线程栈回收,在进程B中这些线程栈就真正成了垃圾。
测试程序:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <linux/unistd.h>
void *func(void *param) {
sleep(10000);
}
void create_threads(int n) {
pthread_t t;
for (int i = 0; i < n; i++)
pthread_create(&t, 0, func, 0);
}
int main(int argc, char* argv[]) {
if (argc != 4) {
printf("usage: %s (parent_thread_num) (child_thread_num) (is_direct_fork)\n", argv[0]);
return 0;
}
create_threads(atoi(argv[1]));
if (!(*argv[3] == '1' ? syscall(__NR_fork) : fork())) {
sleep(10);
create_threads(atoi(argv[2]));
}
sleep(10000);
return 0;
}

转载地址:http://lxmra.baihongyu.com/

你可能感兴趣的文章
python实现登录查询(可以模糊查询)
查看>>
LAMP架构(apache用户认证,域名重定向,apache访问日志)
查看>>
PHP设计模式:原型模式
查看>>
struts2.0的json操作
查看>>
SQL注入神器——sqlmap
查看>>
Unity导航 (寻路系统Nav Mesh Agent)
查看>>
SaltStack配置语法-YAML和Jinja
查看>>
运用免费OA让你有意想不到的效果
查看>>
一些软件设计软则
查看>>
Linux运维基础命令
查看>>
使用PowerShell配置IP地址
查看>>
第十一章 MySQL运算符
查看>>
JAVA常见算法题(十七)
查看>>
GUI鼠标相关设置
查看>>
使用 <Iframe>实现跨域通信
查看>>
闭包--循序学习
查看>>
项目实战之集成邮件开发
查看>>
解决C3P0在Linux下Failed to get local InetAddress for VMID问题
查看>>
1531 山峰 【栈的应用】
查看>>
巧用美女照做微信吸粉,你会做吗?
查看>>