是因为协程将来可能会被抢占吗?或者它允许在关键段使用yield from(我认为不应该鼓励这样做)?
你使用它的原因与在线程化代码中使用锁的原因相同:用于保护关键部分。 asyncio
主要用于单线程代码中,但仍然会发生并发执行(每当遇到 yield from
或 await
时),这意味着有时需要同步。
例如,考虑一个从Web服务器获取数据并缓存结果的函数:
async def get_stuff(url):
if url in cache:
return cache[url]
stuff = await aiohttp.request('GET', url)
cache[url] = stuff
return stuff
现在假设您有多个并发运行的协程,这些协程可能需要使用get_stuff
的返回值:
async def parse_stuff():
stuff = await get_stuff("www.example.com/data")
# do some parsing
async def use_stuff():
stuff = await get_stuff("www.example.com/data")
# use stuff to do something interesting
async def do_work():
out = await aiohttp.request("www.awebsite.com")
# do some work with out
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(
parse_stuff(),
use_stuff(),
do_work(),
))
现在,假设从 url
获取数据非常缓慢。如果同时运行 parse_stuff
和 use_stuff
,每个方法都将承担通过网络获取 stuff
的全部成本。如果你使用锁来保护该方法,就可以避免这种情况:
stuff_lock = asyncio.Lock()
async def get_stuff(url):
async with stuff_lock:
if url in cache:
return cache[url]
stuff = await aiohttp.request('GET', url)
cache[url] = stuff
return stuff
需要注意的是,当一个协程在执行get_stuff
函数并发起aiohttp
调用时,而另一个协程在等待stuff_lock
的情况下,第三个协程完全不需要调用get_stuff
也可以运行,而不会被在Lock
上阻塞的协程所影响。
显然,这个例子有些虚构,但希望它能让您了解为什么asyncio.Lock
很有用;它允许您保护关键部分,而不会阻塞其他不需要访问该关键部分的协程运行。
yield from
并进行阻塞式 HTTP 请求,那么您不也会获得相同的性能吗?因为按照现在的锁定方式,只有一个 get_stuff()
实例会一直在进行中(也就是说,当 A 实例完全完成时,B 实例才能开始)。 - Nick Chammasget_stuff
,那么性能将是相同的。但如果有其他运行其他不需要锁的方法的协程,那么阻塞的 HTTP 请求会阻止所有这些其他协程,完全停止应用程序直到请求完成。使用 asyncio.Lock
可以让所有这些其他协程继续运行。 - danoyield from
或 await
调用的关键部分就需要在 asyncio
中需要锁定。 - dano一个例子是当你只想让某些代码运行一次,但是需要被多个人请求(例如在Web应用程序中)
async def request_by_many():
key = False
lock = asyncio.Lock()
async with lock:
if key is False:
await only_run_once()
async def only_run_once():
while True:
if random()>0.5:
key = True
break
await asyncio.sleep(1)
asyncio.Lock
对象一次,并将其存储在每个调用者都可以访问的地方,例如作为某个共享对象的属性。目前的情况是,每次调用都会实例化一个新的锁,并且继续执行,就好像它不存在一样。 - Seb