Python Threads - критический раздел
Что такое "критический раздел" потока (в Python)?
Нить входит в критический раздел путем вызова метода получения(), который могут быть блокированы или неблокирующая. Нить выходит из критический раздел, позвонив release().
- Понимание Threading в Python, Linux Gazette
Также, какова цель блокировки?
Ответы
Ответ 1
Критический раздел кода - это тот, который может выполняться только одним потоком за раз. Возьмите, например, сервер чата. Если у вас есть поток для каждого соединения (то есть каждый конечный пользователь), один "критический раздел" - это код буферизации (отправка входящего сообщения всем клиентам). Если несколько потоков пытаются сгенерировать сообщение сразу, вы получите переплетенное соединение BfrIToS mANtwD PIoEmesCEsaSges, что явно не подходит.
Блокировка - это то, что можно использовать для синхронизации доступа к критическому разделу (или ресурсам в целом). В нашем примере чат-сервера блокировка похожа на запертую комнату с пишущей машиной. Если один поток там (чтобы напечатать сообщение), ни одна другая нить не может попасть в комнату. Как только первая нить завершена, он открывает дверь и уходит. Затем в комнату может идти другая нить (запирание). "Активизация" замка означает "я получу комнату".
Ответ 2
Другие люди дали очень хорошие определения. Вот классический пример:
import threading
account_balance = 0 # The "resource" that zenazn mentions.
account_balance_lock = threading.Lock()
def change_account_balance(delta):
global account_balance
with account_balance_lock:
# Critical section is within this block.
account_balance += delta
Скажем, что оператор +=
состоит из трех подкомпонентов:
- Считать текущее значение
- Добавьте RHS к этому значению
- Запишите накопленное значение обратно в LHS (технически свяжите его в терминах Python)
Если у вас нет оператора with account_balance_lock
, и вы выполняете два вызова change_account_balance
параллельно, вы можете в конечном итоге чередовать три операции подкомпонента опасным образом. Скажем, вы одновременно вызываете change_account_balance(100)
(AKA pos) и change_account_balance(-100)
(AKA neg). Это может произойти:
pos = threading.Thread(target=change_account_balance, args=[100])
neg = threading.Thread(target=change_account_balance, args=[-100])
pos.start(), neg.start()
- pos: читать текущее значение → 0
- neg: читать текущее значение → 0
- pos: добавить текущее значение для чтения → 100
- neg: добавить текущее значение для чтения значения → -100
- pos: записать текущее значение → account_balance = 100
- neg: записать текущее значение → account_balance = -100
Поскольку вы не заставляли операции выполняться в дискретных кусках, вы можете иметь три возможных результата (-100, 0, 100).
Оператор with [lock]
представляет собой единую неделимую операцию, которая гласит: "Позвольте мне быть единственным потоком, выполняющим этот блок кода. Если что-то еще выполняется, это круто - я буду ждать". Это гарантирует, что обновления для account_balance
являются "потокобезопасными" (parallelism -safe).
Примечание.. В этой схеме есть оговорка: вы должны помнить, что нужно приобретать account_balance_lock
(через with
) каждый раз, когда вы хотите управлять account_balance
для кода остаются потокобезопасными. Есть способы сделать это менее хрупким, но ответ на другой вопрос.
Изменить: В ретроспективе, вероятно, важно отметить, что оператор with
неявно вызывает блокировку acquire
на блокировке - это часть "Я буду ждать" над диалогом над потоком. Напротив, неблокирующий приобретатель говорит: "Если я не могу сразу получить блокировку, дайте мне знать", а затем полагается на вас, чтобы проверить, есть ли у вас замок или нет.
import logging # This module is thread safe.
import threading
LOCK = threading.Lock()
def run():
if LOCK.acquire(False): # Non-blocking -- return whether we got it
logging.info('Got the lock!')
LOCK.release()
else:
logging.info("Couldn't get the lock. Maybe next time")
logging.basicConfig(level=logging.INFO)
threads = [threading.Thread(target=run) for i in range(100)]
for thread in threads:
thread.start()
Я также хочу добавить, что основная цель блокировки - гарантировать атомарность приобретения (неделимость acquire
по потокам), что простой логический флаг не гарантирует. Семантика атомных операций, вероятно, также является содержанием другого вопроса.
Ответ 3
A "критический раздел" - это фрагмент кода, в котором для корректности необходимо убедиться, что только один поток управления может быть в этом разделе за раз. В общем, вам нужен критический раздел, чтобы содержать ссылки, которые записывают значения в память, которые могут совместно использоваться более чем одним одновременным процессом.