进程与线程(一)
- 并发编程
- 并发与并行
- 高并发
- 进程
- 特征
- 什么是进程?线程?
- 进程与程序的区别
- 进程与线程区别
- 进程的五状态
- 进程的种类
- 查看进程命令
- ps aux
- ps axj
- pstree
- kill
- 进程的创建
- fork函数
- fork总结
- vfork函数
- fork与vfork区别
- 获取进程ID和父ID
- 进程的退出
- 僵尸进程和孤儿进程
- 僵尸进程
- 孤儿进程
- 回收僵尸进程
- wait函数(阻塞)
- waitpid函数(可阻塞可不阻塞)
并发编程
并发与并行
- 并发是指在一个时间段内有多个进程在执行
- 并行指的是在同一时刻有多个进程在同时执行
注:如果在只有一个CPU的情况,是无法实现并行的,因为同一时刻只能有一个进程被调度执行,如果此时同时要执行其他进程则必须上下文切换,这只能称为并发,而如果是多个CPU的情况下,就可以同时调度多个进程,这种就可以称之为并行
高并发
- 高并发是使用技术手段使系统可以并行处理很多请求
影响高并发因素,应对措施
进程
- 进程是程序的一次动态运行,是一个具有一定独立功能的程序在数据集合上依次动态执行的过程
特征
- 动态性,进程是程序的一次动态执行过程,进程有明确的生命周期
- 并发性,指多个进程可以同时存在于内存中,并且能够在一段时间内同时执行
- 独立性,进程是一个能够独立运行、独立获得资源和独立接受调度的基本单位,每个进程都有其独立的进程控制块(PCB)
- 异步性,每个进程都已不可预知的速度向前推进,因此OS需要提供相应的同步机制
- 结构性,进程由程序段、数据段和进程控制信息三部分组成
什么是进程?线程?
进程:进程是操作系统进行资源分配和调度的基本单位,它是执行中的程序的一个实例,包括程序代码、当前活动、相关的数据结构以及一个或多个执行线程。
线程:线程是进程内的一个执行单元,也是处理器调度的基本单位。多个线程可以并行执行于同一个进程中,共享该进程的内存和资源。
进程与程序的区别
- 进程是程序的一个运行过程,是一个动态概念;而程序是一组有序的指令集合,是一种静态概念
- 进程是程序的一次执行过程,它是动态的创建和消亡的,具有一定的生命周期,是暂时存在的;而程序是一组代码的集合,它是永久存在的,可以长期保存
- 一个进程可以执行一个或几个程序,一个程序也可以构成多个进程,进程可以创建进程,而程序不能形成新的进程
- 进程和程序的组成不同,从静态角度看,进程由程序、数据和进程控制块三部分组成,而程序是一组有序的指令集合
进程与线程区别
- 资源拥有:进程是资源分配的基本单位,拥有独立的地址空间和系统资源(如文件描述符);而线程作为进程的一部分,与同进程内的其他线程共享大部分资源。
- 调度和切换开销:进程间的切换和创建比线程更消耗资源,因为涉及到虚拟内存、全局变量等资源的复制或分配;线程之间的上下文切换更快,因为它们共享同一地址空间。
- 并发与通信:线程使得并发执行成为可能,同一进程内的线程间通信更为直接高效(通过共享内存);而进程间通信通常需要使用IPC(进程间通信)机制,如管道、套接字等,相对复杂且开销
大。
进程的五状态
进程的种类
- 交互进程:该进程需要人机交互,用户需要给该进程一个信息的反馈。(shell终端)
- 批处理进程:也称批处理文本或者批处理脚本,他们采用轮询的概念去依次执行。
- 守护进程:守护进程(Daemon)是运行在后台的一种特殊进程,它独立于用户的终端会话,没有控制终端,通常用于执行后台任务,如系统服务、监控、日志记录等。守护进程的特点是生命周期长,通常从系统启动时开始运行,直到系统关闭才停止。
查看进程命令
ps aux
ps axj
pstree
以树形关系呈现当前终端所有进程的所属关系
kill
常用的:杀死进程
方法:kill -9 pid; //pid为需要杀死的进程ID号
进程的创建
进程都是被进程创建出来的
1号进程:init进程
init进程创建出一个systemd进程!
终端也是一个进程,也是被创建出来的
fork函数
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
小于0,代表创建子进程失败,更新perror值
等于0,是在子进程中返回
大于0,是在父进程中返回,且含义为子进程的PID
fork总结
- 会完美拷贝父进程中的所有资源(堆栈,代码段,数据段…)
- 子进程会从fork的下一句话开始,会存在父子进程,通过返回值来判断是父进程还是子进程在执行
- 子进程永远都是在fork的下一句开始执行的(会用到fork之前定义的资源)
- 父子进程的执行顺序不确定
- 父子进程的地址空间独立,互不影响
vfork函数
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
//用法同fork()!!!
原理:
vfork()函数创建新进程时并不复制父进程的地址空间,刚开始是运行在父进程的地址空间上的,且保障了子进程会优先被执行,当子进程执行了exec或者exit之后,父进程才有可能被调度。如果在调用exec或者exit之前,子进程依赖于父进程的进一步动作,则会导致死锁!
注意:使用vfork函数之后,子进程内部必须使用exec或者exit函数,否则会造成死锁的现象。
fork与vfork区别
联系:
fork() 和 vfork() 都是用于在Linux系统中创建新进程的系统调用
区别
- 资源复制:
fork(): 当调用 fork() 时,父进程的所有资源(包括内存空间、打开的文件描述符等)都会被复制到子进程中,形成一个几乎完全独立的新进程。这意味着子进程拥有父进程数据的副本,对这些数据的修改不会影响父进程。
vfork(): 而 vfork() 创建的子进程共享父进程的地址空间,不复制父进程的资源。子进程的任何写操作实际上会发生在父进程的内存上,因此必须非常小心,以避免干扰父进程的状态。vfork设计用于那些子进程将立即执行 exec() 系列函数(替换当前进程映像为新程序)的场景,这样可以避免不必要的资源复制开销。 - 执行顺序:
fork(): 父进程和子进程的执行顺序不确定,由操作系统调度器决定。
vfork(): 系统保证子进程先于父进程运行,直到子进程调用 exec() 或 _exit() 。在这之前,父进程会被阻塞,以防止子进程修改的数据影响到父进程的状态。 - 使用场景:
fork(): 由于其创建的进程是独立的,适用于大多数需要创建新进程的场景。
vfork(): 由于其资源不复制的特性,主要用于那些子进程将立即执行新程序,不需要或很少需要修改共享内存的场景。这减少了创建新进程的开销,提高了效率,但也限制了其适用范围。 - 安全性:
使用 vfork() 需要特别小心,因为不当的使用可能导致死锁或数据损坏。子进程应避免执行除了调用 exec() 或 _exit() 之外的任何操作,尤其是避免对共享内存空间进行写操作。
总结而言, fork() 是创建独立新进程的通用方法,而 vfork() 则是一个优化手段,适用于特定场景下快速执行新程序而无需复制父进程资源的情况。在现代编程实践中,由于 fork() 的灵活性和安全性,它更为常见,而 vfork() 主要用于特定的性能敏感或资源受限的环境。
获取进程ID和父ID
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);//获取当前进程的ID号
pid_t getppid(void);//获取当前进程的父进程ID号
进程的退出
#include <stdlib.h>
#include <unistd.h>
void exit(int status);
void _exit(int status);
status是一个整型的参数,可以利用这个参数传递进程结束的状态。通常0表示正常结束;
其他的数值表示出现了错误,进程非正常结束。在实际编程时,可以用wait系统调用接收子进程的返回值,进行相应的处理。
- 退出前:会做缓冲区的清空
- 退出前,不会检查是否有缓冲区内容未被清空,直接退出
僵尸进程和孤儿进程
僵尸进程
- 子进程先于父进程退出,而父进程没有回收子进程的退出资源(一小部分的PCB:struct_task的结构体),此时的子进程就会沦为僵尸进程
- 僵尸进程危害:内存泄漏
- 避免方法
父进程应该及时调用wait()或waitpid()来回收子进程的终止状态,或者使用signal(SIGCHLD, SIG_IGN)忽略子进程退出信号,避免产生僵尸进程。另一种方式是在fork()之后使用double fork技巧,确保中间进程迅速终止,由init进程处理其后续。
孤儿进程
- 当一个进程的父进程终止,而它还在运行,那么这个子进程就变成了孤儿进程。孤儿进程会被init系统进程(PID为1)或会被systemd进程收养自动收养。
- 通常不需要特别处理,因为系统会自动将其收养。
回收僵尸进程
wait函数(阻塞)
- 调用该函数使进程阻塞,直到任意一个子进程结束或者是该进程接收到了一个信号为止。如果该进程没有子进程或者其子进程已经结束,wait函数会立即返回
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int* wstatus)
- (int* wstatus)关心子进程状态,就传入一个int类型指针
- 不关心子进程状态,就传入NULL
- 子进程的结束状态可由Linux中的一些特定的宏来测定
- 函数返回值:
成功:返回子进程的进程号
失败:返回-1
waitpid函数(可阻塞可不阻塞)
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options)
参数:
pid:
pid>0:只等待进程ID等于PID的子进程,不管有其他进程退出,只要指定的子进程还没有结束,waitpid就会一直等
pid = -1:等待任何一个子进程退出,此时和wait一样
pid = 0:等待其组ID等于调用进程的组ID的任一子进程
pid<-1:等待其组ID等于pid的绝对值的任意子进程
status同wait
options:
WNOHANG:若由pid指定的子进程并不立即可用,则waitpid不阻塞,此时返回值为0
0:同wait,阻塞父进程,等待子进程退出
返回值:
正常:结束的子进程进程号
使用选项WNOHANG,且没有子进程结束时,返回0
调用出错:-1