Есть ли способ сделать блокировки python в очереди? До сих пор я предполагал, что threading.lock работает в очереди. Похоже, он просто блокирует случайный шкафчик. Это плохо для меня, потому что программа (игра), которую я работаю, сильно зависит от получения сообщений в правильном порядке. Есть ли в python блокировки? Если да, то сколько я потеряю во время обработки?
Как я должен останавливать свои блокировки Python?
Ответ 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()
). При необходимости повторите.