Python中的多进程、多线程和协程 (6)

Python中的多进程、多线程和协程

多线程的变量机制 import threading lock_n = threading.Lock() n = 0 def inc_n(m): global n lock_n.acquire(blocking=True) n += m lock_n.release() threads = [threading.Thread(target=inc_n, args=(i,)) for i in range(1,11)] [t.start() for t in threads] [t.join() for t in threads] print(n) 55

由上可见,不同的线程之间共享运行环境(比如上面的变量n)。

lock.acquire(blocking=True) 会一直阻塞直到锁空出来为止;一旦空出来就会把它锁上。

并发处理:协程

不同的过程在同一个线程内交替执行。每个协程在运行时独占资源,一段运行结束后自阻塞,等待着被外部(如main函数)控制它的代码唤醒。

相比多线程的优点:轻量级(在解释器层面实现,不需要内核来做切换)、数量不限。

和多线程一样,不同协程之间共用运行环境。

用简单的生成器实现协程 def sum(init): s = init while True: delta = yield s # output s, and then input delta s += delta g = sum(0) print(next(g)) # run entil receiving the first output print(g.send(1)) # send the first input, and then get the second output print(g.send(2)) # send the second input, and then get the third output 0 1 3

上例中只是演示了生成器的自阻塞,以及生成器与其调用者之间的交互。

更进一步,还可以定义多个生成器执行不同的过程,并在main函数里进行对它们的调度(比如实现一个任务队列),从而实现协程。

用回调函数(callback)将普通函数变为协程 def calc(n,callback): r = 0 for i in range(n): r += i callback() def pause(): print('pause') yield # just pause, do not return anything g = calc(10,pause) 用async/await实现协程

相比生成器实现的优点:可以在等待IO/等待网络通信等情况下时阻塞当前协程执行其他协程(而且不会中断等待IO/通信)以节省时间(而只用生成器则无法做到);使用更灵活、方便。

多线程其实也有前一个优点。所以CPython下的多线程也并不是毫无用处,但它的用处是协程用处的子集。

一个注意事项:若想通过协程加速IO,必须使用python中专门的异步IO库才行。

基础使用 import time start = time.perf_counter() def sayhi(delay): time.sleep(delay) print(f'hi! at {time.perf_counter() - start}') def main(): sayhi(1) sayhi(2) main() hi! at 1.0040732999914326 hi! at 3.015253899997333 import time import asyncio start = time.perf_counter() async def sayhi(delay): await asyncio.sleep(delay) print(f'hi! at {time.perf_counter() - start}') async def main(): sayhi1 = asyncio.create_task(sayhi(1)) sayhi2 = asyncio.create_task(sayhi(2)) await sayhi1 await sayhi2 # asyncio.run(main()) # use outside IPython await main() # use inside IPython hi! at 1.0037910000100965 hi! at 2.0026504999987083

上面的程序中:

async 声明当前函数是一个协程。这一声明使得函数体内可以使用create_task和await,也使得该函数本身可以被create_task和await。

一旦一个函数 f 被声明为协程,f()做的事就不再是运行 f,而只是创建一个协程对象并返回之(这个对象并不会自动被运行)。需要使用 asyncio 中的相关工具来运行这个对象。

run(main()) 表示开始执行协程 main() 。要求 main() 必须是“主协程”,即它是整个程序中所有协程的入口(类似主函数)。一个程序中 run(main()) 只应被调用一次,且在 main() 之外不应有任何协程被调用。

run(main()) 是阻塞的。协程的并发特性只有在main()内部才会显现,从外部来看这就是一个普普通通的黑箱调用。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpgdgd.html