如何排队我的Python锁?

时间:2013-10-30 16:30:31

标签: python multithreading synchronization locking

有没有办法让python锁排队?到目前为止,我一直在我的代码中假设threading.lock在队列上运行。看起来它只是锁定一个随机的锁定器。这对我不好,因为我正在工作的程序(游戏)高度依赖于以正确的顺序获取消息。 python中是否有排队的锁?如果是这样,我会在处理时间上损失多少?

3 个答案:

答案 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

这是一个小小的测试驱动程序,可以用显而易见的方式更改QLockthreading.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样式解锁,但不保证它会是按顺序完成。