python协程和子进程混用编程尝试

使用python编程,当程序是IO密集型,很多网友都推荐使用协程代替线程,因为python的多线程因为GIL的原因,并不能使用计算机CPU多核;而协程是微线程,性能更好,资源消耗更少,适合于多并发。

如果程序是计算密集型,则推荐使用多进程编程,因为多进程可以利用到计算机的多核CPU并行计算。

当程序复杂时,必不可少的可能会使用协程和多进程混合编程。

这里做了一个协程和多进程混合编程的尝试,测试代码如下:

 1 import asyncio
 2 import time
 3 import threading
 4 from multiprocessing import Process
 5 import os
 6 
 7 async def task_a(pid):
 8     print("===begin task a.===")
 9     for i in range(5):
10         print("in task_a = %s" % pid)
11         await asyncio.sleep(1)
12         print("Resuming task_a = %s" % pid)
13     print("===finish task a.===")
14     return
15 
16 
17 async def task_b(pid):
18     print("===begin task b.===")
19     p = Process(target=new_loop)
20     p.start()
21     p.join()
22     print("===finish task b.===")
23 
24 
25 async def task_c(pid):
26     print("===begin task c.===")
27     for i in range(5):
28         print("in task_c = %s" % pid)
29         await asyncio.sleep(2)
30         print("Resuming task_c = %s" % pid)
31     print("===finish task c.===")
32     return
33 
34 
35 def new_loop():
36     new_p_loop = asyncio.get_event_loop()
37     print("in new process, pid = {}, ppid={}".format(os.getpid(), os.getppid()))
38 
39     tasks = [task_c(os.getpid())]
40     new_p_loop.run_until_complete(asyncio.wait(tasks))
41     print("finished pid=%s" % (os.getpid()))
42 
43 
44 if __name__ == '__main__':
45     print("main pid={}, ppid={}".format(os.getpid(), os.getppid()))
46     start = time.perf_counter()
47 
48     loop = asyncio.get_event_loop()
49     tasks = [task_a(os.getpid()), task_b(os.getpid())]
50     loop.run_until_complete(asyncio.wait(tasks))
51 
52     end = time.perf_counter()
53     print("cost time = %s" % (end - start))

代码在主进程中使用了协程,创建了两个task:task_a, task_b,这个用于模仿主进程是并发接受消息,然后执行task任务。

task_b中启动了子进程,子进程中又同时启动了协程loop,执行task_c。这个用于模仿task_b是个计算密集型任务,适合启动子进程进行任务执行,从而不影响主进程的运行。

测试代码运行后,打印结果如下:

 1 main pid=125948, ppid=128144
 2 ===begin task a.===
 3 in task_a = 125948
 4 ===begin task b.===
 5 in new process, pid = 121040, ppid=125948
 6 ===begin task c.===
 7 in task_c = 121040
 8 Resuming task_c = 121040
 9 in task_c = 121040
10 Resuming task_c = 121040
11 in task_c = 121040
12 Resuming task_c = 121040
13 in task_c = 121040
14 Resuming task_c = 121040
15 in task_c = 121040
16 Resuming task_c = 121040
17 ===finish task c.===
18 finished pid=121040
19 ===finish task b.===
20 Resuming task_a = 125948
21 in task_a = 125948
22 Resuming task_a = 125948
23 in task_a = 125948
24 Resuming task_a = 125948
25 in task_a = 125948
26 Resuming task_a = 125948
27 in task_a = 125948
28 Resuming task_a = 125948
29 ===finish task a.===
30 cost time = 14.116450799999999

从测试结果来看,程序运行并没有按照我们的预期执行。task_a单独执行需要耗时5秒,task_c单独执行需要耗时10秒,程序执行完耗时14秒多,感觉像串行执行,并没有达到并行执行效果。

从打印日志也可以看出,程序先执行task_a,等待task_a sleep后,切换到执行task_b,task_b启动子进程,然后是先执行完task_c,然后才继续执行task_a,两者没有并行执行。

经过尝试,发现问题出现task_b的p.join()语句上。p.join()的目的是等待子进程执行结束,再退出。然而task_b在执行p.join()语句阻塞时,不会切换到task_a执行,而是将整个主进程的主线程阻塞了。

现将task_b函数代码修改如下:

 1 async def task_b(pid):
 2     print("===begin task b.===")
 3     p = Process(target=new_loop)
 4     p.start()
 5     while True:
 6         if not p.is_alive():
 7             break
 8         else:
 9             await asyncio.sleep(1)
10     print("===finish task b.===")

程序运行结果如下:

main pid=119276, ppid=128048
===begin task a.===
in task_a = 119276
===begin task b.===
in new process, pid = 131008, ppid=119276
===begin task c.===
in task_c = 131008
Resuming task_a = 119276
in task_a = 119276
Resuming task_a = 119276
in task_a = 119276
Resuming task_c = 131008
in task_c = 131008
Resuming task_a = 119276
in task_a = 119276
Resuming task_a = 119276
in task_a = 119276
Resuming task_c = 131008
in task_c = 131008
Resuming task_a = 119276
===finish task a.===
Resuming task_c = 131008
in task_c = 131008
Resuming task_c = 131008
in task_c = 131008
Resuming task_c = 131008
===finish task c.===
finished pid=131008
===finish task b.===
cost time = 11.0163683

从打印日志来看,代码修改后,task_a和task_c基本是并行执行,耗时为11秒。

总结:

协程task之间并发执行,其实是在同一个线程中切换执行,task切换条件是task有I/O操作或者是asyncio.sleep()操作。

像p.join()操作不会触发task之间的切换操作,会导致协程task阻塞。

 

posted @ 2022-12-16 11:41  慕流  阅读(188)  评论(0编辑  收藏  举报