Python中的协程
背景知识
以下背景知识来自文章 https://zhuanlan.zhihu.com/p/137057192
协程的实现
在Python中有多种方式可以实现协程,例如:
greenlet,是一个第三方模块,用于实现协程代码(Gevent协程就是基于greenlet实现)
yield,生成器,借助生成器的特点也可以实现协程代码。
asyncio,在Python3.4中引入的模块用于编写协程代码。
async & awiat,在Python3.5中引入的两个关键字,结合asyncio模块可以更方便的编写协程代码。
说实话我在各大博客,论坛见到的各式各样,总结下来其实也就这几种
协程可以通过一个线程在多个上下文中进行来回切换执行。
生活中不也是这样的么,假设 你是一家制造汽车的老板,员工点击设备的【开始】按钮之后,在设备前需等待30分钟,然后点击【结束】按钮,此时作为老板的你一定希望这个员工在等待的那30分钟的时间去做点其他的工作。
碰到IO阻塞时间长的任务没必要一直等下去,所以涉及到异步编程。新开一个线程专门做等的任务的话,麻烦,还要考虑锁资源,消耗锁,等等。所以引入协程的概念
实战
LeetCode上有多线程的例题,这里打算尝试拿协程写一遍
!以下代码由本人编写,不保证没有隐患
有些可能只是为了出效果而出效果。。。此时建议考虑多进程,多线程
以下代码更多的是为了熟悉asyncio而不是给出问题的解决方案
lc1114. 按序打印
LeetCode上有多线程的例题,
一道easy是让你按次序打印firstsecondthird
拿Python中的协程来写,一种写法就是下面这种
TIO
import asyncio
import time
class Foo:
def __init__(self):
pass
async def first(self) -> None:
print("First",end="")
await asyncio.sleep(2)
async def second(self) -> None:
print("Second",end="")
await asyncio.sleep(2)
async def third(self) -> None:
print("Third",end="")
await asyncio.sleep(2)
t_start=time.time()
loop=asyncio.get_event_loop()
a=Foo()
task1=a.first()
task2=a.second()
task3=a.third()
tasks=[task1, task2, task3]
loop.run_until_complete(asyncio.gather(*tasks))
t_end=time.time()
print("\n"+f"TOTAL_TIME:{t_end-t_start}")
运行结果如上,
解释的话,按照官方文档的说法:
协程函数: 定义形式为 async def 的函数;
协程对象: 调用 协程函数 所返回的对象。
所以3个task都是协程对象,tasks是一个迭代器,此迭代器的每个元素都是协程对象
loop=asyncio.get_event_loop()定义一个事件循环对象
这里asyncio.sleep()替代了一个高耗时的IO操作,在真实场景中可能就是一个高IO耗时的操作(比如网络IO等等)
await asyncio.sleep() 表示await后很可能是一个IO密集的操作,于是挂起,切换到下一个协程(如果有)运行
由于是异步的,总耗时大约为2s而不是约6s
lc1115. 交替打印FooBar
我写了一份协程版的代码. TIO
import asyncio
import time
class FooBar:
def __init__(self, n):
self.n = n
async def foo(self) -> None:
for i in range(self.n):
print("Foo",end="")
await asyncio.sleep(1)
async def bar(self) -> None:
for i in range(self.n):
print("Bar",end="")
await asyncio.sleep(1)
a=FooBar(3)
tasks = [
asyncio.ensure_future(a.foo()),
asyncio.ensure_future(a.bar())
]
loop = asyncio.get_event_loop()
time_start=time.time()
loop.run_until_complete(asyncio.wait(tasks))
time_end=time.time()
print("\n"+f"TOTAL_TIME:{time_end-time_start}")
FooBarFooBarFooBar
TOTAL_TIME:3.0205185413360596
进程已结束,退出代码为 0
asyncio 的同步原语
asyncio 具有下列基本同步原语:
- Lock
- Event
- Condition
- Semaphore
- BoundedSemaphore
asyncio 原语不是线程安全的
lc1116. 打印零与奇偶数
LeetCode上一道middle,打印010203040506...这样的序列
我写了个协程版的如下,(这道题用信号量做容易想,弄3个信号量,最先开始的置1,其余置0.按照要求加减即可)
# 只是asyncio编程练手,感觉有种为了出效果而出效果的感觉
import asyncio
class ZeroEvenOdd:
def __init__(self, n):
self.n = n + 1
self.Zero = asyncio.Semaphore(1)
self.Even = asyncio.Semaphore(0)
self.Odd = asyncio.Semaphore(0)
async def zero(self) :
for i in range(1, self.n):
await self.Zero.acquire()
try:
print(0, end="")
finally:
if i % 2 == 1:
self.Odd.release()
else:
self.Even.release()
async def even(self) :
for i in range(1, self.n):
if i % 2 == 0:
await self.Even.acquire()
try:
print(i, end="")
finally:
self.Zero.release()
async def odd(self) :
for i in range(1, self.n):
if i % 2 == 1:
await self.Odd.acquire()
try:
print(i, end="")
finally:
self.Zero.release()
a=ZeroEvenOdd(5)
loop=asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather( #由于有了同步机制,gather中协程对象的次序可以随便放
a.odd(),
a.zero(),
a.even()
))
0102030405
进程已结束,退出代码为 0