通过fork()函数创建进程
在Unix/Linux操作系统中,通过Python的os模块中封装的fork()函数可以轻松地创建一个进程。fork()函数的声明如下:
fork()
以上函数调用后,操作系统会建立当前线程的副本以实现进程的创建,此时原有的进程被称为父进程,复制的进程被称为子进程。需要注意的是,fork()函数的一次调用产生两个结果:若当前执行是父进程,fork()函数返回子进程ID;若当前执行的进程是子进程,fork()函数返回0。如果fork()函数调用时出现错误,进程创建失败,将返回一个负值。
下面使用fork()函数创建一个子进程,让父进程和子进程分别执行不同的任务,代码如下:
import os
import time
value = os.fork() # 创建子进程
if value == 0: # 子进程执行if分支语句
print('---子进程---')
time.sleep(2)
else: # 父进程执行else分支语句
print('---父进程---')
time.sleep(2)
以上程序调用fork()函数创建子进程,使用变量value记录fork()的返回值,并根据fork()的返回结果区分父进程与子进程,为这两个进程分派不同的任务:当value为0时,说明当前进程是子进程,执行if分支中的语句;当value不为0时,说明此时系统调度的是父进程,执行else分支中的语句。进程创建与程序执行的具体流程如图1所示。
图1 使用fork()函数创建进程
程序执行一次的结果如下所示:
---父进程---
---子进程---
观察此次结果可以推测,系统先调度父进程,再调度子进程,但实际上,子进程和父进程执行的顺序是不确定的,会受到时间片、调度优先级或其它因素的影响。
若程序中顺序调用两次fork()函数,那么第一次调用fork()后系统中存在的两个进程都会调用第二个fork()函数创建新进程,两次fork()函数后进程的变化如图2所示。
图2 进程的变化
从图2中可以看出,“父进程1”和“子进程1”再次复制出两个子进程,“父进程1”成为“子进程2”的父进程,“子进程1”成为“子进程3”的父进程,变成“父进程2”。
下面使用fork()函数创建3个子进程,代码如下:
import os
import time
print('---第一次fork()调用---')
value = os.fork() # 创建子进程,此时进程的总数量为2
if value == 0: # 子进程执行if分支语句
print('---进程1---')
time.sleep(2)
else: # 父进程执行else分支语句
print('---进程2---')
time.sleep(2)
print('---第二次fork()调用---')
value = os.fork() # 创建子进程,此时进程的总数量为4
if value == 0: # 子进程执行if分支语句
print('---进程3---')
time.sleep(2)
else: # 父进程执行else分支语句
print('---进程4---')
time.sleep(2)
程序执行的结果如下:
---第一次fork()调用---
---进程2---
---进程1---
---第二次fork()调用---
---进程4---
---进程4---
---进程3---
---进程3---
由执行结果可知,程序在第一次调用fork()函数后创建了一个子进程,此时共有父进程和子进程执行下面的代码,分别输出 “---进程2---”和“---进程1---”;程序在第二次调用fork()函数后又创建了两个新的子进程,此时共有两个父进程和两个子进程执行下面的代码,分别输出两次“---进程4---”和“---进程3---”。
多学一招:获取当前进程的ID
进程ID是进程的唯一标识,为了便于管理系统中的进程,os模块提供了os.getpid()函数和os.getppid()函数来分别获取当前进程id和当前进程父进程的id,示例代码如下:
import os
process = os.fork() # 创建子进程
if process == 0:
# 获取父进程的ID
print('我是子进程-%d,父进程是%d'%(os.getpid(), os.getppid()))
else:
print('我是父进程-%d, 子进程是%d'%(os.getpid(), process)) # 获取当前线程的ID
程序运行的结果如下:
我是父进程-2497, 子进程是2498
我是子进程-2498,父进程是2497