有没有办法让Python锁排队?到目前为止,我在代码中一直假设 threading.lock 在队列上操作。看起来它只是把锁给了一个随机的储物柜。这对我来说很糟糕,因为我正在工作的程序(游戏)高度依赖于以正确的顺序获取消息。 python中有队列锁吗?如果是这样,我会损失多少处理时间?
我完全同意那些声称你可能以一种徒劳的方式思考这个问题的评论。 锁提供序列化,并且“根本不”旨在提供排序。 执行订单的标准、简单且可靠的方法是使用 Queue.Queue
也就是说,我将展示一种实现“先进先出锁”的方法。 它既不难也不容易 - 介于两者之间 - 你不应该使用它;-)恐怕只有你可以回答你的“我会在处理时间上损失多少?”问题 - 我们不知道您使用锁的频率,或者您的应用程序引发了多少锁争用。 不过,通过研究这段代码,您可以得到一个粗略的感觉。
import threading, collections
class QLock:
def __init__(self):
self.lock = threading.Lock()
self.waiters = collections.deque()
self.count = 0
def acquire(self):
self.lock.acquire()
if self.count:
new_lock = threading.Lock()
new_lock.acquire()
self.waiters.append(new_lock)
self.lock.release()
new_lock.acquire()
self.lock.acquire()
self.count += 1
self.lock.release()
def release(self):
with self.lock:
if not self.count:
raise ValueError("lock not acquired")
self.count -= 1
if self.waiters:
self.waiters.popleft().release()
def locked(self):
return self.count > 0
这是一个小测试驱动程序,可以通过明显的方式更改它以使用此
QLock
或
threading.Lock
:def work(name):
qlock.acquire()
acqorder.append(name)
from time import sleep
if 0:
qlock = threading.Lock()
else:
qlock = QLock()
qlock.acquire()
acqorder = []
ts = []
for name in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
t = threading.Thread(target=work, args=(name,))
t.start()
ts.append(t)
sleep(0.1) # probably enough time for .acquire() to run
for t in ts:
while not qlock.locked():
sleep(0) # yield time slice
qlock.release()
for t in ts:
t.join()
assert qlock.locked()
qlock.release()
assert not qlock.locked()
print("".join(acqorder))
刚刚在我的盒子上,使用
threading.Lock
运行 3 次产生了以下输出:
BACDEFGHIJKLMNOPQRSTUVWXYZ
ABCDEFGHIJKLMNOPQRSUVWXYZT
ABCEDFGHIJKLMNOPQRSTUVWXYZ
所以这当然不是随机的,但也不是完全可预测的。 使用
QLock
运行它,输出应该始终是:
ABCDEFGHIJKLMNOPQRSTUVWXYZ
我担心的是,如果锁没有按 FIFO 顺序释放,很可能会发生线程饥饿,这对我的软件来说将是可怕的。
读完之后,我消除了恐惧,意识到每个人都在说:如果你想要这个,那你就做错了。另外,我确信您可以依靠操作系统来完成其工作,而不会让您的线程挨饿。
为了达到这一点,我做了一些挖掘以更好地了解锁在 Linux 下的工作原理。我首先查看了 pthreads (Posix Threads) glibc 源代码和规范,因为我在 Linux 上使用 C++ 工作。我不知道 Python 是否在底层使用 pthreads,但我假设它可能是。
我在周围的多个 pthreads 参考中没有找到任何与解锁顺序相关的规范。
我发现:Linux 上 pthread 中的锁是使用名为
futex 的内核功能实现的。
http://man7.org/linux/man-pages/man2/futex.2.htmlhttp://man7.org/linux/man-pages/man7/futex.7.html https://www.kernel.org/doc/ols/2002/ols2002-pages-479-495.pdf它解释了一些关于解锁策略、futex 的工作原理以及在 Linux 内核中的实现方式,等等。
在那里我找到了我想要的东西。它解释了 futexes 在内核中的实现方式,使得解锁主要以 FIFO 顺序完成(以增加公平性)。但是,这并不能保证,并且一个线程可能会暂时跳线。他们允许这样做不会使代码过于复杂,并允许他们实现良好的性能,而不会因为强制执行 FIFO 顺序的极端措施而失去它。
所以基本上,你所拥有的是:
POSIX 标准对互斥锁的锁定和解锁顺序没有任何要求。任何实现都可以自由地按照自己的意愿进行,因此如果您依赖此顺序,您的代码将无法移植(即使在同一平台的不同版本之间也是如此)。
pthreads 库的 Linux 实现依赖于称为 futex 的功能/技术来实现互斥体,并且它主要尝试对互斥体进行 FIFO 样式解锁,但不能保证会按该顺序完成。