有没有办法让python锁排队?到目前为止,我一直在我的代码中假设threading.lock在队列上运行。看起来它只是锁定一个随机的锁定器。这对我不好,因为我正在工作的程序(游戏)高度依赖于以正确的顺序获取消息。 python中是否有排队的锁?如果是这样,我会在处理时间上损失多少?
答案 0 :(得分:5)
我完全赞同这些评论,声称你可能会以无用的方式思考这个问题。锁提供序列化,并且根本不旨在提供排序。执行订单的沼泽标准,简单且可靠的方法是使用Queue.Queue
CPython将其留给操作系统来决定获取锁定的顺序。在大多数系统中,这似乎或多或少是“随机的”。这是无法改变的。
那就是说,我将展示一种实现“FIFO锁定”的方法。这既不难也不容易 - 介于两者之间 - 你不应该使用它;-)我担心只有你能回答“我在处理时间上会损失多少?”问题 - 我们不知道您使用锁的程度有多大,或者您的应用程序引发了多少锁竞争。不过,通过研究这段代码,你可以获得粗略的感觉。
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
答案 1 :(得分:1)
是的,您可以使用线程ID列表创建FIFO队列:
FIFO = [5,79,3,2,78,1,9...]
您将尝试获取锁定,如果不能,则将尝试线程的ID(FIFO.insert(0,threadID)
)推送到队列的前面,每次释放锁定时,确保如果一个线程想要获取锁,它必须在队列的末尾有一个线程ID(threadID == FIFO[-1]
)。如果线程在队列末尾有线程ID,那么让它获取锁定,然后将其弹出(FIFO.pop()
)。根据需要重复。
答案 2 :(得分:1)
我偶然发现了这篇文章,因为我有类似的要求。或者至少我是这么认为的。
我担心如果锁没有以FIFO顺序释放,那么很可能会发生线程饥饿,这对我的软件来说太可怕了。
读了一下之后,我解除了我的恐惧并意识到每个人都在说:如果你想要这个,那你就错了。此外,我确信你可以依靠操作系统来完成它的工作,而不是让你的线程挨饿。
为了达到这一点,我做了一些挖掘,以更好地了解锁在Linux下的工作原理。我首先看一下pthreads(Posix Threads)的glibc源代码和规范,因为我在Linux上使用C ++。我不知道Python是否使用了pthreads,但我会假设它可能是。
我没有找到关于解锁顺序的多个pthreads引用的任何规范。
我发现:Linux上的pthreads中的锁是使用名为 futex 的内核功能实现的。
http://man7.org/linux/man-pages/man2/futex.2.html
http://man7.org/linux/man-pages/man7/futex.7.html
这些页面中第一页的参考文献链接引导您阅读PDF:
https://www.kernel.org/doc/ols/2002/ols2002-pages-479-495.pdf
它解释了一些关于解锁策略的内容,以及关于futex如何在Linux内核中实现和实现的内容,以及更多内容。
在那里我找到了我想要的东西。它解释了futexes在内核中的实现方式使得解锁主要以FIFO顺序完成(以提高公平性)。但是,这并不能保证,并且一个线程可能会偶尔跳过一行。它们允许这不会使代码过于复杂,并且由于强制执行FIFO命令的极端措施而允许它们实现的良好性能而不会丢失它。
基本上,你所拥有的是:
POSIX标准并未对互斥锁的锁定和解锁顺序提出任何要求。任何实现都可以随意进行,因此如果您依赖此订单,您的代码将无法移植(甚至不能在同一平台的不同版本之间)。
pthreads库的Linux实现依赖于一个名为futex的功能/技术来实现互斥锁,它主要尝试对互斥锁进行FIFO样式解锁,但不保证它会是按顺序完成。