3.3 理解进程
关于进程与(线程)的问题,相关讨论一直很多,但当我们真正接触到的时候,可能还是会发现并没有吃透。
1. 理解进程(模型)
进程是资源分配的最小单位,线程是CPU调度的最小单位。进程是程序的一次执行,由内核来控制,有进程队列task_struck。程序由代码段,数据段,堆,栈组成内核通过调用exec加载这些数据到内存中,这些是进程的资源。每个进程都有自己独立的资源。
而线程只是进程中的某一个模块或者功能的执行,他们共享所在进程的怎个数据段和堆段,只有自己独立的栈段。
进程中定义了相关的信息和资源来确保对进程的操纵和控制,操作系统有一个叫PCB的结构来专门来存储这些信息和资源。例如,PCB结构保存如下的信息:
- Process ID: 这是一个无符号整型数据,标识操作系统中的唯一进程。
- 程序计数器: 对应下一条要执行的程序指令地址。
- I/O信息: 包含一组打开的文件和进程相关的设备。
- 内存分配: 该区域保存进程已经使用内存空间、为该进程预留的内存空间和页表信息。
- CPU调度: 该区域保存进程优先级信息(and points to the staggering queues)
- 优先级: 定义进程获取CPU资源的优先级。
- 当前状态: 表述该进程是准备状态、等待状态还是运行状态。
- CPU申请: 保存栈指针和其他信息。
2. 定义进程状态
进程整个生命周期具有三种状态,分别如下:
- 运行状态: 进程正占用cpu资源。
- 准备状态: 处于进程队列中的进程已经准备好获取cpu资源。
- 等待状态: 进程正在等待执行中的任务所需的I/O操作。
3. Python 实现简单的多进程
要让Python程序实现多进程(multiprocessing),我们先了解操作系统的相关知识。
Unix/Linux操作系统提供了一个fork()系统调用,它非常特殊。普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。
子进程永远返回0,而父进程返回子进程的ID。这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID。
Python的os模块封装了常见的系统调用,其中就包括fork,可以在Python程序中轻松创建子进程,让我们感受一下:
import os
print('Process (%s) start...' % os.getpid())
pid = os.fork()
if pid == 0:
print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))
else:
print('I (%s) just created a child process (%s).' % (os.getpid(), pid))
运行结果如下:
Process (12565) start...
I (12565) just created a child process (12566).
I am child process (12566) and my parent is 12565.
由于Windows没有fork调用,上面的代码在Windows上无法运行的!
有了fork调用,一个进程在接到新任务时就可以复制出一个子进程来处理新任务,常见的Apache服务器就是由父进程监听端口,每当有新的http请求时,就fork出子进程来处理新的http请求。