简述
asyncio即异步IO,是python3.7的一个亮点。在IO密集的场景下,对我们的编程非常有帮助,切记IO密集的场景而不是计算密集的场景。
asyncio的API分为高等和低等。一般情况下,我们使用高级API即可,当高级API不能满足的时候再使用低级API。下文中也主要讲述高级API。
献上官网地址:https://docs.python.org/3/library/asyncio.html
来个hello world, 进入异步IO的世界
"""
@author: hananmin
@time: 2019/12/14 12:15
"""
import asyncio
import time
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
task1 = asyncio.create_task(
say_after(1, 'hello'))
task2 = asyncio.create_task(
say_after(2, 'world'))
print(f"started at {time.strftime('%X')}")
# Wait until both tasks are completed (should take
# around 2 seconds.)
await task1
await task2
print(f"finished at {time.strftime('%X')}")
if __name__ == "__main__":
asyncio.run(main())
代码输出如下图,嗯,没啥特殊的呀
请把task1的say_after的第一个入参改为3,即 say_after(3, “hello”) , 运行一下:
就问神奇不?? 这就是异步的效果,task1和task2都因为sleep导致阻塞,但是task2先睡醒,所以 “world” 先输出
async 和 await
async 装饰的函数,代表这个函数是协程(Coroutines)。await装饰的表达式,代表这个表达式有可能发生阻塞,如果出现阻塞,该await所在的协程就会暂停运行,让其他协程运行。
可以被await装饰的对象:
- Coroutines
- tasks,当多个协程需要并发执行的时候,需要把协程转化为task,下文会讲到
- Futures,这是个低级的对象(low-level),一般不会用到,代表着异步操作的最终结果,通过他可以判断异步任务是否在执行
并发执行
不知道你有没有发现,hello world其实就是并发运行的,开始和结束的时间间隔由最长的任务决定。我们举一个不是并行的例子,也就是不使用tasks:
"""
@author: hananmin
@time: 2019/12/14 12:15
"""
import asyncio
import time
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
# task1 = asyncio.create_task(
# say_after(3, 'hello'))
#
# task2 = asyncio.create_task(
# say_after(2, 'world'))
print(f"started at {time.strftime('%X')}")
# Wait until both tasks are completed (should take
# around 2 seconds.)
await say_after(1, 'hello')
await say_after(2, 'world')
print(f"finished at {time.strftime('%X')}")
if __name__ == "__main__":
asyncio.run(main())
运行结果如下,我们看出没有使用task的情况,是不可并发执行的
- 创建task
asyncio.create_task(coro, *, name=None)
入参是协程
- 并发运行task
asyncio.gather(*aws, loop=None, return_exceptions=False)
入参可以是协程,这样会自动被转化为task运行。该方法也是可以被await装饰的。
- timeout
如果想给异步任务添加一个超时时间,那么可以使用wait_for()
方法,看一个例子:
import asyncio
import datetime
async def eternity():
# Sleep for one hour
await asyncio.sleep(2)
print('yay!')
async def main():
# Wait for at most 1 second
try:
await asyncio.wait_for(eternity(), timeout=1.0)
except asyncio.TimeoutError:
print('timeout!')
if __name__ == "__main__":
asyncio.run(main())
运行结果如下,说好的让你睡1秒,你却睡了两秒,那么我们只能无情的把你锤醒了:
-
asyncio.wait(aws, *, loop=None, timeout=None, return_when=ALL_COMPLETED)
这个方法也是等待任务,但是和wait_for有些区别:
(1)首先wait接收的是一个集合,而wait_for接收的是一个可等待对象
(2)return_when可以设置返回的条件(FIRST_COMPLETED:只要有一个任务异常或者完成就返回;FIRST_EXCEPTION:有一个异常发生就返回;ALL_COMPLETED等待所有任务完成)
(3)wait方法超时的时候不会抛出asyncio.TimeoutError异常,也不会调用task的cancel()方法 -
asyncio.as_completed(aws, *, loop=None, timeout=None)
等待所有的异步动作完成,可以设置超时时间,超时就会抛出asyncio.TimeoutError -
asyncio.run_coroutine_threadsafe(coro, loop)
当你从一个线程调用,另一个线程中的协程的时候,请使用这个方法,这个方法是线程安全的
同步问题
当多个协程访问共享资源的时候,就需要锁这个东西。asyncio提供了几种锁,但是这些锁不是线程安全的,所以不可以跨越线程使用,下面我们来看看这几种锁
- Lock
当某个协程给一个资源加锁了,其他的协程就只能等待。
上一段使用锁的代码:
import asyncio
# 穷人
async def poor(lock: asyncio.Lock, name, money):
print(f"Im poor {name}, i need money!!!")
await lock.acquire()
try:
print(f'Yes!!! i get {money["value"]}')
if money['value'] > 0:
money['value'] = 0
finally:
lock.release()
async def main():
# 钱
money = {"value": 10}
# 锁
lock_obj = asyncio.Lock()
# 开始抢钱
done = asyncio.as_completed({poor(lock_obj, "XM", money), poor(lock_obj, "XH", money)})
for f in done:
await f
if __name__ == "__main__":
asyncio.run(main())
程序输出:
- Event
当Event是unset状态的时候,允许多个协程等待他变为set状态。当它被设置为set状态的时候,等待的协程都会收到通知。
来一段代码:
import asyncio
# 有钱人
async def rich(event: asyncio.Event, name):
print(f"Im rich {name}, i am coming!!!")
await asyncio.sleep(2)
# 发钱
print(f'Yes!!! distribute money')
event.set()
await asyncio.sleep(3)
# 停止发钱
print(f'Fuck!!! Stop!!!')
event.clear()
# 穷人
async def poor(event: asyncio.Event, name):
print(f"Im poor {name}, i need money!!!")
await event.wait()
while event.is_set():
print(f'{name}: Yes!!! i get money')
await asyncio.sleep(1)
async def main():
# 锁
event_obj = asyncio.Event()
# 开始抢钱
done = asyncio.as_completed({rich(event_obj, "han"), poor(event_obj, "XM"), poor(event_obj, "XH")})
for f in done:
await f
if __name__ == "__main__":
asyncio.run(main())
运行结果:
- Condition
这个比较强,是Event和Lock的结合。协程必须获取锁才能等待被通知,同样,只有获取锁才能通知其他协程。当协程接收到通知,就可以做其他的事情了。
上一段代码:
import asyncio
import datetime
# 有钱人
async def rich(condition: asyncio.Condition, name):
print(f"{str(datetime.datetime.now())} Im rich {name}, i am coming!!!")
await asyncio.sleep(1)
await condition.acquire()
# 发钱
try:
print(f'{str(datetime.datetime.now())} Hi!!! Come in')
finally:
condition.release()
await asyncio.sleep(1)
await condition.acquire()
try:
condition.notify(1)
await asyncio.sleep(1)
condition.notify(1)
finally:
condition.release()
# 穷人
async def poor(condition: asyncio.Condition, name):
print(f"{str(datetime.datetime.now())} Im poor {name}, i need money!!!")
await asyncio.sleep(1.5)
await condition.acquire()
try:
print(f"{name}: {str(datetime.datetime.now())} Im coming!!!")
count = 0
await condition.wait()
while count < 3:
print(f'{name}: {str(datetime.datetime.now())} Yes!!! i get money')
count = count + 1
finally:
condition.release()
async def main():
# 锁
condition_obj = asyncio.Condition()
# 开始抢钱
await asyncio.wait({rich(condition_obj, "han"), poor(condition_obj, "XM"), poor(condition_obj, "XH"),
poor(condition_obj, "XZ")}, timeout=5)
if __name__ == "__main__":
asyncio.run(main())
运行结果:
- Semaphore
资源数量是固定,每当协程访问资源时,数量就减少一个,访问完毕后就释放资源(资源数量加一);而当数量被消耗完后,其他的协程就只能等待了。
上一段代码:
import asyncio
import datetime
# 穷人
async def poor(sem: asyncio.Semaphore, name):
print(f"{str(datetime.datetime.now())} Im poor {name}, i need money!!!")
await asyncio.sleep(1.5)
await sem.acquire()
try:
count = 0
while count < 3:
print(f'{name}: {str(datetime.datetime.now())} Yes!!! i get money')
count = count + 1
await asyncio.sleep(1.5)
finally:
sem.release()
async def main():
# 锁
sem_obj = asyncio.Semaphore()
# 开始抢钱
await asyncio.wait({poor(sem_obj, "XM"), poor(sem_obj, "XH")}, timeout=5)
if __name__ == "__main__":
asyncio.run(main())
运行结果:
如果对本文有疑问或者发现不对的地方,希望能给予评论或者进群630300475,讨论一下。先写到这里了,到了游戏时间了,还有一部分,找时间补上