我们都知道进程是操作系统进行资源分配和调度的基本单位,在单核 CPU 中,同一时刻只能运行单个进程,虽然仍然可以同时运行多个程序,但进程之间是通过轮流占用 CPU 来执行的。进程有三种状态,分别是: 阻塞态、就绪态和运行态;
Python 内置的 multiprocessing 模块提供了对多进程的支持,下面我们将一一介绍其用法
创建进程的类 - Process
multiprocessing 模块提供了一个创建进程的类 Process,其创建进程有以下两种方法:
- 创建一个 Process 类的实例,并指定目标任务函数;
- 自定义一个类并继承 Process 类,重写其
__init__()
方法和run()
方法;
我们首先使用第一种方法创建两个进程,并与单进程运行的时间作比较。定义耗时任务,并比较单进程和多进程耗时(multi_process.py)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29import os
import time
from multiprocessing import Process
def task_process(delay):
num = 0
for i in range(delay*100000000):
num += i
print(f"进程 PID 为 [{os.getpid()}], 执行完成")
if __name__ == '__main__':
print(f'父进程 PID 为 [{os.getpid()}]')
t0 = time.time()
task_process(3)
task_process(3)
t1 = time.time()
print(f"顺序执行程序耗时 {t1 - t0}")
p0 = Process(target=task_process, args=(3,))
p1 = Process(target=task_process, args=(3,))
t2 = time.time()
p0.start()
p1.start()
p0.join()
p1.join()
t3 = time.time()
print(f"多进程并发执行耗时 {t3 - t2}")上面的代码定义了一个上亿次数据累加的耗时函数,在运行结束时打印调用此函数的进程 ID,在第 17 和 18行是单进程执行,第 21 和 22 行分别实例化了 Process 类,并制定目标函数为 task_process,第 24~27 行是双进程并行执行,执行后打印耗时。其运行结果如下
1
2
3
4
5
6
7父进程 PID 为 [56634]
进程 PID 为 [56634], 执行完成
进程 PID 为 [56634], 执行完成
顺序执行程序耗时 17.692678928375244
进程 PID 为 [56980], 执行完成
进程 PID 为 [56979], 执行完成
多进程并发执行耗时 9.836258888244629我们发现多进程执行相同的操作耗时更少。
使用第二种方法实现,自定义一个类并继承 Process 类(multi_process2.py)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30import os
import time
from multiprocessing import Process
class MyProcess(Process):
def __init__(self, delay):
self.delay = delay
super().__init__()
def run(self):
""" 子进程要执行的代码 """
num = 0
for i in range(self.delay * 100000000):
num += i
print(f"进程 PID 为 [{os.getpid()}], 执行完成")
if __name__ == '__main__':
print(f"父进程 PID 为 [{os.getpid()}]")
p0 = MyProcess(3)
p1 = MyProcess(3)
t0 = time.time()
p0.start()
p1.start()
p0.join()
p1.join()
t1 = time.time()
print(f"多进程并发执行耗时 {t1 - t0}")提示: 进程 p0, p1 在调用
start()
时,自动调用其run()
方法运行结果如下
1
2
3
4父进程 PID 为 [72556]
进程 PID 为 [72560], 执行完成
进程 PID 为 [72559], 执行完成
多进程并发执行耗时 9.102619886398315
Process 类的构造函数
下面我们看一下 Process 类还有哪些功能可以使用,该类的构造函数原型如下
1 | class multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None) |
参数说明如下:
Target
: 表示调用对象,一般为函数,也可以为类;Args
: 表示调用对象的位置参数,元组类型;Kwargs
: 表示调用对象的字典;Name
: 为进程的别名;Group
: 参数不使用,可忽略;
类提供的常用方法如下:
is_alive()
: 返回进程是否激活的;join([timeout])
: 阻塞进程,直到进程执行完成或超时或进程被终止;run()
: 代表进程执行的任务函数,可被重写;start()
: 激活进程;terminate()
: 终止进程;
其属性如下:
authkey
: 字节码,进程的准密钥daemon
: 父进程终止后自动终止,且不能产生新进程,必须在 start() 之前设置;exitcode
: 退出码,进程在运行时为 None,如果为 -N,就表示被信号 N 结束;name
: 获取进程名称;pid
: 进程 ID。
不设置 daemon 属性示例(multi_process_no_daemon.py)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import time
from multiprocessing import Process
def task_process(delay):
""" 子进程要执行的代码 """
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} 子进程执行开始.")
print(f"sleep {delay}s")
time.sleep(delay)
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} 子进程执行结束.")
if __name__ == '__main__':
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} 父进程执行开始.")
p0 = Process(target=task_process, args=(3,))
p0.start()
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} 父进程执行结束.")这里没有使用
p0.join()
来阻塞进程。运行结果如下1
2
3
4
52023-03-24 17:36:07 父进程执行开始.
2023-03-24 17:36:07 父进程执行结束.
2023-03-24 17:36:07 子进程执行开始.
sleep 3s
2023-03-24 17:36:10 子进程执行结束.可以看出,父进程并没有等待子进程运行完毕就打印了退出信息,程序依然会等待子进程运行完成。
设置 daemon 属性示例(multi_process_daemon.py)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import time
from multiprocessing import Process
def task_process(delay):
""" 子进程要执行的代码 """
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} 子进程执行开始.")
print(f"sleep {delay}s")
time.sleep(delay)
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} 子进程执行结束.")
if __name__ == '__main__':
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} 父进程执行开始.")
p0 = Process(target=task_process, args=(3,))
p0.daemon = True # 设置 daemon 的属性为 True
p0.start()
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} 父进程执行结束.")运行结果如下
1
22023-03-24 17:40:53 父进程执行开始.
2023-03-24 17:40:53 父进程执行结束.程序并没有等待子进程结束而结束,只要主程序运行结束,程序即退出