Что такое «критическая секция» потока (в Python)?
A thread enters the critical section by calling the acquire() method, which can either be blocking or non-blocking. A thread exits the critical section, by calling the release() method.
- Понимание потоковой передачи в Python, Linux Gazette
Кроме того, какова цель блокировки?






Критический раздел кода - это тот, который может выполняться только одним потоком за раз. Возьмем, к примеру, чат-сервер. Если у вас есть поток для каждого соединения (то есть для каждого конечного пользователя), один «критический раздел» - это код спулинга (отправка входящего сообщения всем клиентам). Если несколько потоков попытаются спутировать сообщение одновременно, вы получите переплетение BfrIToS mANtwD PIoEmesCEsaSges, что, очевидно, совершенно бесполезно.
Блокировка - это то, что можно использовать для синхронизации доступа к критическому разделу (или ресурсам в целом). В нашем примере с чат-сервером замок похож на запертую комнату с пишущей машинкой. Если там присутствует один поток (для вывода сообщения), другой поток не сможет попасть в комнату. Как только первая нить закончена, он открывает комнату и уходит. Затем в комнату может войти еще один поток (запереть его). «Получение» замка просто означает «я получу комнату».
Попробуйте рассказать об этом ребятам из Erlang. Это может быть неправильно во многих языках программирования, но, поскольку это настолько распространено (и дает такой полезный пример), я решил пойти с этим. На вопрос о пуле подключений я бы сказал другое :)
«критическая секция» - это фрагмент кода, в котором для правильности необходимо гарантировать, что только один поток управления может находиться в этом разделе одновременно. В общем, вам нужен критический раздел, содержащий ссылки на значения написать в памяти, которые могут использоваться более чем одним параллельным процессом.
Ваш совет может запутать новичка. Похоже, вы говорите, что можно читать из памяти, которая используется несколькими потоками без блокировки, что, конечно, не так (если вы не уверены, что ваши записи являются атомарными).
Я не уверен, что мы не согласны. Чтения в порядке так долго как, записи атомарные. Каждый может читать константу без необходимости в критической секции.
Да, но если вы пишете массив данных, или список, или dict, в то время как другой поток читает его, он может быть прочитан в промежуточном состоянии, если чтение также не является «атомарным», с использованием того же семафора / замок.
Что, конечно, является следствием того факта, что доступ к массивам и т. д. Не является атомарным.
Другие люди дали очень хорошие определения. Вот классический пример:
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
Допустим, оператор += состоит из трех подкомпонентов:
Если у вас нет оператора 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()
Поскольку вы не заставляли операции выполняться отдельными частями, у вас может быть три возможных результата (-100, 0, 100).
Оператор with [lock] представляет собой единую неделимую операцию, которая гласит: «Позвольте мне быть единственным потоком, выполняющим этот блок кода. Если выполняется что-то еще, это круто - я подожду». Это гарантирует, что обновления account_balance являются «поточно-ориентированными» (безопасными для параллелизма).
Примечание: У этой схемы есть предостережение: вы должны не забывать приобретать 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 между потоками), чего не гарантирует простой логический флаг. Семантика атомарных операций, вероятно, также является содержанием другого вопроса.
-1 за распространение очень неправильного и плохого выбора дизайна: уродливый ужасный подход одна резьба на соединение, который распространен, но ошибочен.