Python 多进程编程
Python 多进程编程
多线程编程的复杂之处在于,要仔细地使用锁,来保证数据的完整。多线程的意义在于,可以在 I/O 相关的任务中,取得好的性能。在其它场景中,常见的应用程序没有重度的 I/O,多线程不是唯一的选择,还有其它并发或并行方案可以使用。这种方案是多进程方案。在 Python 中使用多进程可以避免 Python 的全局解释锁(GIL,Global Interpreter Lock)的限制,可以实现并行执行(高层看起来是并行,底层依然是并发)。如果应用程序是在多核CPU上运行,多进程编程尤其有益。在现实中,多进程编程,是 Python 可以充分利用多核 CPU 的唯一手段。
在 CPU 的多个进程之间,本质上默认没有共享内存。这意味着数据破坏的机率非常小。如果两个进程有共享数据,他们需要使用一些进程间通信机制。Python 支持进程间通信。
接下来,我们首先讲解一下创建进程的基本知识,后续会讲解进程间通信。
创建进程
Python 提供了 multiprocessing 包来支持多进程编程,它和多线程编程包非常像。multiprocessing 包包含了两个实现多进程的方案,一个是使用 Process对象,一个是使用 Pool 对象。接下来,我们逐一讨论这两个方案。
使用 Process 对象
通过创建一个 Process 对象,可以生成一个进程,接下来,可以使用 start 方法来启动它。下面是其示例代码:
import os
from multiprocessing import Process, current_process as cp
from time import sleep
def print_hello():
sleep(2)
print("{} - {}: 你有房有车有女/男朋友吗?".format(os.getpid(), cp().name))
def print_message(msg):
sleep(1)
print("{} - {}: {}".format(os.getpid(), cp().name, msg))
def main():
processes = []
# 创建进程
processes.append(Process(target=print_hello, name = "进程 1"))
processes.append(Process(target=print_hello, name = "进程 2"))
processes.append(Process(target=print_message, args=["早上好"], name = "进程 3"))
# 启动进程
for p in processes:
p.start()
# 等待所有进程运行完毕
for p in processes:
p.join()
print("退出主进程。")
if __name__ == '__main__':
main()
上面代码的运行结果是(不一样的电脑会有不一样的运行结果):
6612 - 进程 3: 早上好
6340 - 进程 1: 你有房有车有女/男朋友吗?
11204 - 进程 2: 你有房有车有女/男朋友吗?
退出主进程。
可见,创建进程的代码,是简单明了的。
使用 Pool 对象
Pool 对象提供了一种方便的方式(使用它的 map 方法)创建进程、给每个进程设置一个函数,跨进程分发输入参数。在下面的示例代码中,我们提供了一个大小是 3 的进程池,然而提供了可供 5 个进程使用的参数。把进程池大小设置为 3 是为了确保在同一时间最大有 3 个进程是活动的,不管我们给 Pool 对象的 map 方法传递了多少形参。多余的形参将会在某个子进程处理完它当前的工作后,被传递这个子进程。下面是示例代码:
import os
from multiprocessing import Process, Pool, current_process as cp
from time import sleep
def print_message(msg):
sleep(1)
print("{} - {}: {}".format(os.getpid(), cp().name, msg))
def main():
# 在进程池中创建进程
with Pool(3) as pool:
pool.map(print_message, ["提子", "葡萄", "苹果", "桔子", "梨"])
print("退出主进程。")
if __name__ == '__main__':
main()
上面代码的输出结果是:
8124 - SpawnPoolWorker-2: 提子
13556 - SpawnPoolWorker-1: 苹果
13384 - SpawnPoolWorker-3: 葡萄
8124 - SpawnPoolWorker-2: 桔子
13556 - SpawnPoolWorker-1: 梨
将输入参数分配给与一组池进程绑定的函数的神奇之处在于map方法。map方法等待所有函数完成执行,这就是为什么如果使用Pool 对象创建进程,则不需要使用join方法。
Proecess 和 Pool 的区别如下:
使用 Pool 对象 | 使用 Porcess 对象 |
|---|---|
| 只有活动进程被保留在内存里 | 所有创建成功能的进程都在内存里 |
| 更适合大型数据集和重复性任务 | 对小数据集更友好 |
| 进程会阻塞到 I/O 操作上,直到获得 I/O资源 | 进程不会被阻塞到 I/O 操作上 |
<完>
1943

被折叠的 条评论
为什么被折叠?



