【发布时间】:2017-01-17 23:03:22
【问题描述】:
更新:编辑标题以关注主要问题。请参阅我的答案以获取完整更新。
在下面的代码中,a() 和 b() 是相同的。它们中的每一个同时从 0 计数到 9,同时每 2 个计数获取并产生一个锁。
import asyncio
lock = asyncio.Lock()
def a ():
yield from lock.acquire()
for i in range(10):
print('a: ' + str(i))
if i % 2 == 0:
lock.release()
yield from lock.acquire()
lock.release()
def b ():
yield from lock.acquire()
for i in range(10):
print('b: ' + str(i))
if i % 2 == 0:
lock.release()
yield from lock.acquire()
lock.release()
asyncio.get_event_loop().run_until_complete(asyncio.gather(a(), b()))
print('done')
我期望交错输出,但我得到了:
b: 0
b: 1
b: 2
b: 3
b: 4
b: 5
b: 6
b: 7
b: 8
b: 9
a: 0
a: 1
a: 2
a: 3
a: 4
a: 5
a: 6
a: 7
a: 8
a: 9
done
似乎第二个yield实际上并没有让步,而是立即重新获取锁并继续。
这对我来说似乎是一个错误。我对吗?还是有其他解释?
以下代码使用额外的初始“noop”yield 进行修改,可以正常工作。这让我相信锁确实是公平的,而且可能是正确的。
import asyncio
lock = asyncio.Lock()
def a ():
yield from lock.acquire()
yield from asyncio.sleep(0)
for i in range(10):
print('a: ' + str(i))
if i % 2 == 0:
lock.release()
yield from lock.acquire()
lock.release()
def b ():
yield from lock.acquire()
yield from asyncio.sleep(0)
for i in range(10):
print('b: ' + str(i))
if i % 2 == 0:
lock.release()
yield from lock.acquire()
lock.release()
asyncio.get_event_loop().run_until_complete(asyncio.gather(a(), b()))
print('done')
输出:
a: 0
b: 0
a: 1
a: 2
b: 1
b: 2
a: 3
a: 4
b: 3
b: 4
a: 5
a: 6
b: 5
b: 6
a: 7
a: 8
b: 7
b: 8
a: 9
b: 9
done
请注意,我在开始时只进行一次无操作收益,而不是每 2 次。然而,这样做会导致每 2 次交错计数,正如第一段代码中所预期的那样。
调度程序中只有一些优化(我认为是一个错误),当获取没有其他人正在等待的锁时,它并没有真正yield。
如何解释第一个输出?
【问题讨论】:
-
我相信这是异步锁实现中的一个错误。此行之前应该有一个
yield:github.com/python/cpython/blob/master/Lib/asyncio/locks.py#L171 -
你想达到什么目的?
-
@Udi 我只是在学习 asyncio,这看起来像是一个实现问题。我的 github 问题可能会澄清一点:github.com/python/asyncio/issues/486
-
如果您正在使用 asyncio,请尝试解决 IO 绑定问题。
-
出于好奇,您为什么认为这是一个“糟糕”的优化?非竞争锁应该以最小的开销运行(例如,如果 CPython 的
threading.Lock()能够立即获取锁,则不会释放 GIL)。asyncio.Lock的重点是确保 if 您在持有锁的同时返回到事件循环,在您等待某事时没有其他人可以获取锁。如果你真的在你的协程中做了一些并发的事情,锁会很重要,但你不重要(在持有锁的时候没有等待),所以锁无关紧要。
标签: python python-3.x concurrency python-asyncio python-3.6