Потоки Python - критический раздел

Что такое «критическая секция» потока (в 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

Кроме того, какова цель блокировки?

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
12
0
19 170
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Критический раздел кода - это тот, который может выполняться только одним потоком за раз. Возьмем, к примеру, чат-сервер. Если у вас есть поток для каждого соединения (то есть для каждого конечного пользователя), один «критический раздел» - это код спулинга (отправка входящего сообщения всем клиентам). Если несколько потоков попытаются спутировать сообщение одновременно, вы получите переплетение BfrIToS mANtwD PIoEmesCEsaSges, что, очевидно, совершенно бесполезно.

Блокировка - это то, что можно использовать для синхронизации доступа к критическому разделу (или ресурсам в целом). В нашем примере с чат-сервером замок похож на запертую комнату с пишущей машинкой. Если там присутствует один поток (для вывода сообщения), другой поток не сможет попасть в комнату. Как только первая нить закончена, он открывает комнату и уходит. Затем в комнату может войти еще один поток (запереть его). «Получение» замка просто означает «я получу комнату».

-1 за распространение очень неправильного и плохого выбора дизайна: уродливый ужасный подход одна резьба на соединение, который распространен, но ошибочен.

nosklo 07.01.2009 15:25

Попробуйте рассказать об этом ребятам из Erlang. Это может быть неправильно во многих языках программирования, но, поскольку это настолько распространено (и дает такой полезный пример), я решил пойти с этим. На вопрос о пуле подключений я бы сказал другое :)

zenazn 07.01.2009 16:43

«критическая секция» - это фрагмент кода, в котором для правильности необходимо гарантировать, что только один поток управления может находиться в этом разделе одновременно. В общем, вам нужен критический раздел, содержащий ссылки на значения написать в памяти, которые могут использоваться более чем одним параллельным процессом.

Ваш совет может запутать новичка. Похоже, вы говорите, что можно читать из памяти, которая используется несколькими потоками без блокировки, что, конечно, не так (если вы не уверены, что ваши записи являются атомарными).

mhenry1384 07.01.2009 07:27

Я не уверен, что мы не согласны. Чтения в порядке так долго как, записи атомарные. Каждый может читать константу без необходимости в критической секции.

Charlie Martin 07.01.2009 08:00

Да, но если вы пишете массив данных, или список, или dict, в то время как другой поток читает его, он может быть прочитан в промежуточном состоянии, если чтение также не является «атомарным», с использованием того же семафора / замок.

MrWonderful 22.11.2014 00:15

Что, конечно, является следствием того факта, что доступ к массивам и т. д. Не является атомарным.

Charlie Martin 22.11.2014 00:30

Другие люди дали очень хорошие определения. Вот классический пример:

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 являются «поточно-ориентированными» (безопасными для параллелизма).

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

Другие вопросы по теме