其他分享
首页 > 其他分享> > 异步锁优化错误

异步锁优化错误

作者:互联网

更新:编辑标题以关注主要问题.请参阅我的答案以获取完整更新.

在以下代码中,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

似乎第二个收益实际上并没有收益,而是立即重新获得了锁并继续.

对我来说,这似乎是个错误.我对吗?还是有另一种解释?

下面的代码经过修改,并带有额外的初始“空转”产量,可以按预期工作.这使我相信锁定确实是公平的并且可能是正确的.

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个计数发生一次交错.

调度程序中只有一些优化(我认为是一个错误),在获得其他人没有等待的锁时并没有真正产生.

其他如何解释第一个输出?

解决方法:

更新:鉴于我对github问题的评论(link),以下内容已过时.该注释指出,您可以使用Lock.locked()来预测Lock.acquire()是否将屈服.它还观察到在快速情况下无法产生其他许多协程,因此甚至考虑修复所有协程是一个迷失的原因.最后,它涉及到如何解决其他问题,并建议可以更好地解决它.这就是对asyncio.nop()方法的请求,该方法只会让调度程序屈服,而不会做其他任何事情.他们没有添加该方法,而是决定重载asyncio.sleep(0)并对其进行“解优化”(在lock.acquire()讨论的上下文中),以在参数为0时产生给调度程序.

以下是原始答案,但已被上一段取代:

根本原因是asyncio.lock的实现在其first three lines中尝试变得过于聪明,并且如果没有服务员,则不会将控制权交还给调度程序:

if not self._locked and all(w.cancelled() for w in self._waiters):
    self._locked = True
    return True

但是,如我的第一个示例所示,这阻止了其他协程甚至成为服务员.他们只是没有机会跑到试图获取锁的地步.

低效的解决方法是始终在获取锁之前立即从asyncio.sleep(0)屈服.

这是低效的,因为在通常情况下,还会有其他服务员,并且获取锁也将控制权交还给调度程序.因此,在大多数情况下,您将两次将控制权交还给调度程序,这很糟糕.

另请注意,锁文档含糊不清地指出:“此方法将阻塞,直到锁被解锁,然后将其设置为已锁并返回True.”当然给人的印象是,它将在获得锁之前将控制权交给调度程序.

我认为,正确的做法是使锁实现始终屈服并且不要太聪明.另外,锁实现应具有一个方法,该方法告诉您是否在获取锁时将其屈服,以便您的代码在锁获取不会时可手动屈服.另一种选择是让acquire()调用返回一个值,该值告诉您是否实际产生了该值.这不太可取,但仍比现状好.

有人认为更好的解决方法可能是在release()时手动屈服.但是,如果您看到一个紧紧的循环,在工作完成后释放并重新获取,那么就等于是同一件事-在通常情况下,它仍然会产生两倍的收益,一次是在发布时,一次是在获取时,这会增加效率.

标签:python-3-6,python-3-x,concurrency,python-asyncio,python
来源: https://codeday.me/bug/20191026/1935755.html