Подтвердить что ты не робот

Как я должен останавливать свои блокировки Python?

Есть ли способ сделать блокировки python в очереди? До сих пор я предполагал, что threading.lock работает в очереди. Похоже, он просто блокирует случайный шкафчик. Это плохо для меня, потому что программа (игра), которую я работаю, сильно зависит от получения сообщений в правильном порядке. Есть ли в python блокировки? Если да, то сколько я потеряю во время обработки?

4b9b3361

Ответ 1

Я полностью согласен с комментариями, утверждающими, что вы, вероятно, думаете об этом нечестным образом. Замки обеспечивают сериализацию и вовсе не предназначены для обеспечения порядка. Стандартный, простой и надежный способ выполнения заказа - использовать 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)

В моей коробке только 3 запуска с использованием threading.Lock произвели этот вывод:

BACDEFGHIJKLMNOPQRSTUVWXYZ
ABCDEFGHIJKLMNOPQRSUVWXYZT
ABCEDFGHIJKLMNOPQRSTUVWXYZ

Таким образом, это, конечно, не случайно, но и не является полностью предсказуемым. Выполняя его с помощью QLock, выход всегда должен быть:

ABCDEFGHIJKLMNOPQRSTUVWXYZ

Ответ 2

Я наткнулся на этот пост, потому что у меня было подобное требование. Или, по крайней мере, я так и думал.

Мой страх заключался в том, что если бы блокировки не были выпущены в порядке FIFO, то, вероятно, произойдет головоломка с потоком, и это будет ужасно для моего программного обеспечения.

Прочитав немного, я отклонил свои страхи и понял, что все говорят: если вы этого хотите, вы делаете это неправильно. Кроме того, я был убежден, что вы можете полагаться на ОС, чтобы выполнять свою работу и не позволять своему потоку голодать.

Чтобы добраться до этого момента, я немного поработал, чтобы лучше понять, как блокировки работают под Linux. Я начал с того, что посмотрел на исходный код glibc и спецификации pthreads (Posix Threads), потому что я работал на С++ в Linux. Я не знаю, использует ли Python pthreads под капотом, но я предполагаю, что это возможно.

Я не нашел никаких спецификаций в нескольких ссылках на pthreads, связанных с порядком разблокировки.

Я нашел: блокировки в pthreads в Linux реализованы с использованием функции ядра 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

Это немного объясняет стратегии разблокировки и о том, как futexes работают и реализуются в ядре Linux, и намного больше.

И там я нашел то, что хотел. В нем объясняется, что futexes реализованы в ядре таким образом, что разблокировки в основном выполняются в порядке FIFO (для повышения справедливости). Тем не менее, это не гарантируется, и возможно, что один поток может немного перепрыгнуть линию. Они позволяют это не слишком усложнять код и обеспечивать хорошую производительность, которую они достигли, не теряя его из-за экстремальных мер для обеспечения соблюдения порядка FIFO.

Итак, в основном, у вас есть:

Стандарт POSIX не налагает никаких требований относительно порядка блокировки и разблокировки мьютексов. Любая реализация может делать так, как они хотят, поэтому, если вы полагаетесь на этот порядок, ваш код не будет переносимым (даже между разными версиями одной и той же платформы).

Реализация Linux-библиотеки pthreads основана на функции/методе futex для реализации мьютексов и в основном пытается выполнить разблокировку мьютексов стиля FIFO, но не гарантируется, что это будет сделано в этом заказ.

Ответ 3

Да, вы можете создать FIFO очередь, используя список идентификаторов потоков:

FIFO = [5,79,3,2,78,1,9...]

Вы попытались бы получить блокировку, а если не можете, то нажмите на идентификатор попытки (FIFO.insert(0,threadID)) на передней панели очереди, и каждый раз, когда вы отпустите блокировку, убедитесь, что если поток хочет приобретите блокировку, в которой он должен иметь идентификатор потока в конце очереди (threadID == FIFO[-1]). Если поток имеет идентификатор потока в конце очереди, то пусть он получает блокировку, а затем выталкивает ее (FIFO.pop()). При необходимости повторите.