Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How Do I Queue My Python Locks?

Is there a way to make python locks queued? I have been assuming thus far in my code that threading.lock operates on a queue. It looks like it just gives the lock to a random locker. This is bad for me, because the program (game) I'm working is highly dependent on getting messages in the right order. Are there queued locks in python? If so, how much will I lose on processing time?

like image 497
SwiftCore Avatar asked Oct 30 '13 16:10

SwiftCore


People also ask

How do you get locks in Python?

A lock can be locked using the acquire() method. Once a thread has acquired the lock, all subsequent attempts to acquire the lock are blocked until it is released. The lock can be released using the release() method.

How do you queue a thread in Python?

You can make a queue or line of tasks or objects by using the queue library in Python. Simply you can add a task to the queue (using put() method) or get a task out of the line and processes it (using get() method). Threading package in Python let you run multiple tasks at the same time.

Is queue in Python thread-safe?

Thread Programming Luckily, Queue() class has a thread-safe implementation with all the required locking mechanism. So producer and consumer from different threads can work with the same queue instance safely and easily.


1 Answers

I wholly agree with the comments claiming that you're probably thinking about this in an unfruitful way. Locks provide serialization, and aren't at all intended to provide ordering. The bog-standard, easy, and reliable way to enforce an order is to use a Queue.Queue

CPython leaves it up to the operating system to decide in which order locks are acquired. On most systems, that will appear to be more-or-less "random". That cannot be changed.

That said, I'll show a way to implement a "FIFO lock". It's neither hard nor easy - somewhere in between - and you shouldn't use it ;-) I'm afraid only you can answer your "how much will I lose on processing time?" question - we have no idea how heavily you use locks, or how much lock contention your application provokes. You can get a rough feel by studying this code, though.

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

Here's a little test driver, which can be changed in the obvious way to use either this QLock or a 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)

On my box just now, 3 runs using threading.Lock produced this output:

BACDEFGHIJKLMNOPQRSTUVWXYZ
ABCDEFGHIJKLMNOPQRSUVWXYZT
ABCEDFGHIJKLMNOPQRSTUVWXYZ

So it's certainly not random, but neither is it wholly predictable. Running it with the QLock instead, the output should always be:

ABCDEFGHIJKLMNOPQRSTUVWXYZ
like image 106
Tim Peters Avatar answered Nov 15 '22 14:11

Tim Peters