看看进程、线程的父子关系

线程的父子关系

    一般利用pthread库让让主线程去创建子线程,从而形成一个线程的父子关系。

    主线程调用pthread_join来等待子线程的结束,然后释放子进程占有的栈、id、私有数据等资源。这样设计阻塞的好处是父线程会等到子线程结束后才结束,这样不至于父线程关闭导致子线程没有结束就被关闭。可见父子线程默认情况下是有紧密联系的,父线程需要为子线程处理身后事。

    现在,我期望让父子线程没有关系,让子线程在结束的时候自己去释放自己的资源,那么需要调用pthread_detach来解开默认的父子关系。这样,父线程不再会顾忌子线程的结束与否。“不受约束”的子线程很可能因父线程的结束而异常关闭。

#include<iostream>
#include<pthread.h>
using namespace std;


void* fun(void*)
{
	pthread_detach(pthread_self());//父子关系的接触与否依赖于这句,可以观察有和没有的运行结果的区别。
	cout<<"fun begins!"<<endl;
	int i;
	for(i=0;i<1000000000;i++)
		;
	cout<<"fun is over! i="<<i<<endl;
	return NULL;
}


int main()
{
	cout<<"main begins."<<endl;
	pthread_t tid;
	int err = pthread_create(&tid, NULL, fun, NULL);
	void * tret;
	err = pthread_join(tid, &tret);
	cout<<"main is over."<<endl;
	return 0;
}

    从运行结果看出,在不调用pthread_detach的情况下,父线程会在pthread_join阻塞,等候子线程结束后再唤醒并结束。若调用了pthread_detach,父线程会直接关闭,导致子线程被迫关闭。


进程的父子关系

   一般利用系统调用fork在主进程创建子进程(见fork),从而形成一个父子进程。

    同样,一般要求父进程wait子进程结束的信号,然后去处理子进程的“身后事”,否则子进程将会变为僵尸进程(见《僵尸进程和孤儿进程》),显然属于父子关系处理不当的情况。如果父进程比子进程先结束,父子的关系会自然脱离,子进程变为孤儿进程,其“身后事”交给init办理。

    现在,我期望父子进程结束这种关系,成为两个独立的进程。对于线程来说,父子线程即使再怎么独立,也总是属于同一个进程的,因为共享了太多的资源。对于进程来说,父子进程的关系一旦解除之后,就可以认为进程没有关系了,子进程的身后事交给init来解决。

   APUE里提出一个fork两次的方法,通过创建了爷孙三代来使得进程关系彻底脱离,不过借助于第三个进程太麻烦了。利用系统调用setsid(void)就可以直接做到,setsid的作用是将当前进程加入到一个新的会话中。

#include<iostream>
#include<unistd.h>
using namespace std;

int main()
{	

	if(fork() != 0)
	{
		cout<<"father begins."<<endl;
		int i=0;
		for(;i<1000000000;i++)	;
		for(;i<1000000000;i++)	;
		for(;i<1000000000;i++)	;
		for(;i<1000000000;i++)	;
		for(;i<1000000000;i++)	;
		for(;i<1000000000;i++)	;
		cout<<"father is over. i="<<i<<endl;
	}
	else
	{
		cout<<"son begins."<<endl;
		pid_t pid = setsid();//有没有这句:父子关系是否脱离的区别
		cout<<"son is over."<<endl;
	}

	return 0;
}

程序运行起来,在另一个终端ps u观察进程的状态,可以看到父子关系是否脱离情况下子进程的状态的不同。从结果看出,不调用setsid的情况下,子进程提前结束会成为Zombie进程;调用setsid的情况下,子线程提前结束会完整释放其资源。

<think>嗯,用户现在想了解父子进程之间的关系、区别以及通信方式。之前用户已经询问过进程间通信和线程间通信的方式,现在进一步聚焦在父子进程这个特定关系上。 用户可能正在学习操作系统或并发编程的相关内容,需要明确父子进程的特性。从引用内容看,用户已经掌握了进程通信的基础知识(如管道、共享内存等),现在需要更深入理解父子进程这种特殊关系的运作机制。 关于父子进程的关键点需要涵盖: 1. 创建关系:fork()系统调用是核心,子进程是父进程的副本 2. 内存特性:写时复制技术如何平衡内存效率与隔离性 3. 生命周期依赖:父进程需要管理子进程的终止(wait/waitpid) 4. 通信方式:匿名管道作为最典型的父子通信方式,以及信号、共享内存等补充方案 特别要注意用户引用的资料[2][4]中强调的"写时复制"机制和资源继承特性,这是理解父子进程效率的关键。而引用[1][3]提到的匿名管道限制(仅限亲缘进程)正好对应父子进程场景。 在解释通信方式时,需要区分: - 单工通信:匿名管道 - 双向通信:需建立两个管道或使用其他IPC - 异步通知:信号机制 - 高效数据共享:共享内存+同步机制 最后关于"僵尸进程"的警告是必要的实践提示,这关系到程序稳定性。用户可能是开发者,需要知道如何避免资源泄漏。 相关问题的设计要引导用户思考进程设计的实践问题,比如: - 如何选择通信方式(对比管道/共享内存的适用场景) - 多级进程树的管理挑战 - 现代编程语言(如Python)对进程管理的封装</think>### 父子进程关系详解 #### 一、父子进程关系与创建 1. **创建机制** 父进程通过 `fork()` 系统调用创建子进程,此时: - **返回值差异** - 父进程接收子进程的 **PID(>0)** - 子进程接收 **返回值 0**(通过 `getpid()` 获取自身 PID)[^4] - **资源继承** 子进程复制父进程的: - 代码段、数据段、堆栈段 - 文件描述符表 - 信号处理方式 - **例外**:进程ID、父进程ID、未决信号集独立[^2] 2. **写时复制(Copy-On-Write)** - 父子进程**共享物理内存**直到一方尝试修改数据 - 修改时触发缺页中断,内核为修改方分配新物理页 - 显著减少内存复制开销[^3] #### 二、父子进程核心区别 | 特性 | 父进程 | 子进程 | |--------------|----------------------------|----------------------------| | **进程ID** | 原PID不变 | 分配新PID | | **PPID** | 保持不变 | 设置为父进程PID | | **资源统计** | 独立统计CPU/内存使用 | 独立统计资源消耗 | | **文件锁** | 不继承文件锁 | 不继承文件锁 | | **未决信号** | 子进程初始无未决信号 | 继承信号掩码但不继承未决信号 | > **关键差异**:子进程不继承父进程的: > - 进程锁(`flock`) > - 定时器(`setitimer/alarm`) > - 异步I/O操作 #### 三、父子进程通信方式 1. **匿名管道(Pipe)** - **仅限父子进程** 通过 `pipe()` 创建单向通道,典型用法: ```c int fd[2]; pipe(fd); // fd[0]读端, fd[1]写端 if (fork() == 0) { close(fd[1]); // 子进程关闭写端 read(fd[0], buf, size); } else { close(fd[0]); // 父进程关闭读端 write(fd[1], data, size); } ``` - **特点**:半双工、内核缓冲区同步[^1][^3] 2. **信号(Signal)** - 父进程用 `kill(pid, SIGUSR1)` 通知子进程 - 子进程通过 `signal(SIGUSR1, handler)` 处理 - **局限**:仅传递信号编号,无数据负载[^2] 3. **共享内存(Shared Memory)** - 父进程创建共享内存段: ```c int shmid = shmget(IPC_PRIVATE, size, 0666); char *shm = shmat(shmid, NULL, 0); ``` - 子进程通过 `shmat()` 附加同一内存段 - **需同步**:需配合信号量/互斥锁[^1][^5] 4. **文件/内存映射** - 父进程 `mmap()` 映射文件到内存 - 子进程继承文件描述符访问同一映射区 #### 四、生命周期管理 1. **进程终止** - 子进程退出后成为**僵尸进程**(保留退出状态) - 父进程必须通过 `wait()`/`waitpid()` 回收资源: ```c int status; pid_t wpid = waitpid(child_pid, &status, 0); ``` - 父进程先退出时,子进程成为**孤儿进程**,由init进程(PID=1)接管 2. **同步控制** - `wait()` 阻塞父进程直至子进程退出 - `waitpid(pid, NULL, WNOHANG)` 非阻塞轮询 > **典型问题**:僵尸进程累积导致PID耗尽——务必回收子进程! --- ### 相关问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值