python--多进程 & 协程

  • 资料来源:

    https://docs.python.org/zh-cn/3/library/multiprocessing.html
    https://www.liaoxuefeng.com/wiki/1016959663602400/1017628290184064

  • 更新

    1
    2021.04.16 初始

导语

python 因为 gil 锁的存在,所以多线程算是鸡肋.若要完全利用 cpu 多进程+协程是推荐的方式.

但是从使用来看,python 支持协程的库还太少,也不能简单的将串行的库改写为支持协程的,因此有些无奈..😔..

先补充有关多进程的内容了,python 协程或许还没有 get 到正确的使用方法.

多进程

资料来源于廖雪峰教程/官方文档.

linux 上创建进程是个著名的调用 fork,但是 win 上并不通用.因此 python 提供了 multiprocessing 库,保证跨平台.

每一个进程被描述成一个 Process 类对象,启动时调用 start 方法.join 这里是等待子进程结束.

1
2
3
4
5
6
7
8
9
from multiprocessing import Process

def f(name):
print('hello', name)

if __name__ == '__main__':
p = Process(target=f, args=('bob',))
p.start()
p.join()

进程池

手动创建 N 多进程很冗余,python 提供了 Pool 类,实现了类似进程池的用法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from multiprocessing import Pool
import os, time, random

def long_time_task(name):
print('Run task %s (%s)...' % (name, os.getpid()))
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print('Task %s runs %0.2f seconds.' % (name, (end - start)))

if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Pool(4)
for i in range(5):
p.apply_async(long_time_task, args=(i,))
print('Waiting for all subprocesses done...')
p.close()
p.join()
print('All subprocesses done.')

apply_async 导入执行函数.

Pool(4) 最好与机器的 cpu 线程数相同.

close 执行后无法再加入新的进程.

进程间通信

multiprocessing 提供了两种近似方法, Queue队列 和 Pipe() 管道.

Queue 是线程和进程安全的,多进程随意读写.

1
2
3
4
5
6
7
8
9
10
11
from multiprocessing import Process, Queue

def f(q):
q.put([42, None, 'hello'])

if __name__ == '__main__':
q = Queue()
p = Process(target=f, args=(q,))
p.start()
print(q.get()) # prints "[42, None, 'hello']"
p.join()

Pipe() 管道,默认是全双工的 ,但是不支持多进程/线程竞争读 or 写.

1
2
3
4
5
6
7
8
9
10
11
12
from multiprocessing import Process, Pipe

def f(conn):
conn.send([42, None, 'hello'])
conn.close()

if __name__ == '__main__':
parent_conn, child_conn = Pipe()
p = Process(target=f, args=(child_conn,))
p.start()
print(parent_conn.recv()) # prints "[42, None, 'hello']"
p.join()

进程间同步

并不推荐在多进程间使用锁,但是 multiprocessing 提供了类似 替代

进程间共享状态

因为暂时没有用到,就不详述了.

详情见 进程间共享状态

编程指导

来着官方文档的 多进程编程指导,只摘取一些记录了.

start方法

  • 避免共享状态.

    • 避免大量进程间通信
    • 通信以 multiprocessing 提供的队列管道最佳 .
  • 继承优先于序列化/反序列化

    • 通常需要避免通过队列/管道发送对象,因此子进程直接从父进程继承这些对象更优先.
  • join

    • 需要避免僵尸进程..无限等待…
    • join 进程池时,必须非常小心共享资源,非常容易造成死锁.
  • 尽力避免 Process.terminate,一旦杀掉进程,其相关的所有资源将不可用,可能造成未知问题.

协程

官方文档: 协程与任务

async/await 语法糖是 python 3.6 以后引入的,因此搜索协程资料稍早一点会有混乱.

更多资料和食用,待后续 get 协程重点再补充.

目前使用协程并没有给程序带来收益.因为用到的库除了 sqlite 外,其他都不支持 协程调用,同时使用了内存盘,sqlite 上协程带来的一点收益也非常不明显.

一些协程资料

https://mp.weixin.qq.com/s?__biz=Mzg3MjU3NzU1OA==&mid=2247495856&idx=1&sn=144acd0252a09c96991cabcdc0f80d1f&source=41#wechat_redirect

结语

py 协程并没有像初次接触 kotlin 协程那样经验,或许还需要等待更熟悉 py 时才能领略吧.

多进程已经可以把 cpu 跑到 100%,但是这样依旧跑了超过 1 个小时,数据处理阶段 $O(n)$ 尚可接受,但是这样跑算法 $O(n^2)$ ~ $O(n^3)$ 实在是无法接受…

或许考虑换 go ?