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