一、进程回收:wait()与waitpid()
子进程退出后,如果父进程不回收它的资源,就会变成僵尸进程。我在学习中主要用两个函数来处理这个问题。
wait()
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
这个函数会阻塞等待,直到有子进程退出才返回。参数wstatus用来保存子进程退出时的状态,不关心的话直接传NULL就行。成功返回回收到的子进程PID,失败返回-1。
waitpid()
pid_t waitpid(pid_t pid, int *status, int options);
比wait更灵活,可以指定回收哪个进程。pid参数有几种情况:
<-1 :回收指定进程组内的任意子进程
-1 :回收任意子进程
0 :回收和当前调用进程同组的所有子进程
>0 :回收指定ID的子进程
options传0就是阻塞回收,传 WNOHANG 就是非阻塞。非阻塞模式下如果没回收到子进程会返回0,适合父进程还有其他任务要做的场景,搭配轮询使用。
二、exec函数簇
有时候我需要在一个进程里执行另一个可执行程序,这时候就用到exec函数簇。它的本质是把外部程序替换到当前进程的文本区,进程本身还是那个进程,只是跑的代码变了。
常用的几个:
int execl(const char *path, const char *arg, ... /* (char *) NULL */);
path是可执行程序的完整路径,比如 "/bin/ls" ,后面跟着执行参数,最后必须以NULL结尾。
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
和execl类似,但它会去PATH环境变量保存的路径里找可执行文件,所以file直接写程序名就行,比如 "ls" 。
int execv(const char *path, char *const argv\[\]);
int execvp(const char *file, char *const argv\[\]);
这两个是把参数放在一个指针数组里传进去,而不是一个个列出来。
命名规律其实挺好记:l代表list(参数列表形式),p代表PATH(走环境变量查找),v代表vector(数组形式传参)。这些函数只有失败时才有返回值,返回-1,成功的话直接就去执行新程序了,不会再回来。
三、线程
- 什么是线程
线程可以理解成轻量级的进程,也是用来实现多任务并发的。有个关键点我记的比较清楚:进程是操作系统资源分配的最小单位,线程是CPU任务调度的最小单位。
线程由所在进程创建,一定属于某个进程。创建线程时,进程只为它分配独立的栈区空间(默认8M),堆区、数据区、文本区、内核区域都是和进程共享的。
- 进程和线程的区别
我整理了一张对比表,面试经常问到:
进程 线程
概念 (进程)资源分配的最小单位 (线程)CPU调度的最小单位
效率 (进程)要分配0-4G虚拟内存,开销大 (线程)创建快,只分配栈区,切换也快
内存 (进程)消耗大,并发量相对少 (线程)只独立栈区,其他共享
安全性 (进程)空间独立,安全性高 (线程)共享空间,安全性低一些
通信 (进程)不能直接通信,需要IPC机制 (线程)可以直接通信,全局变量就行
- 线程的创建与消亡
线程调度也是宏观并行、微观串行,和进程一样。消亡分两步:线程退出,然后回收线程资源空间。
创建线程用 pthread_create :
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
参数说明:
thread:保存线程ID的变量地址
attr:线程属性,传NULL用默认属性
start_routine:函数指针,指向线程要执行的任务函数
arg:传给线程任务函数的参数
成功返回0,失败返回错误码(非0)。
有两个注意点:
-
编译链接时要加线程库参数: -lpthread 或 -pthread
-
创建线程的主进程不能先于线程结束,不然线程就没了