Подтвердить что ты не робот

Является ли назначение переменных Python атомарным?

Скажем, я использую обработчик signal для обработки таймера интервалов.

def _aHandler(signum, _):
  global SomeGlobalVariable
  SomeGlobalVariable=True

Могу ли я установить SomeGlobalVariable, не беспокоясь о том, что в маловероятном сценарии при установке SomeGlobalVariable (т.е. виртуальная машина Python запускает байт-код для установки переменной), что присваивание в обработчике сигнала может что-то сломать? (т.е. метастабильное состояние)

Обновить. Меня особенно интересует случай, когда "составное присвоение" выполняется вне обработчика.

(возможно, я тоже думаю о "низком уровне", и об этом все позаботятся в Python... исходя из фона Embedded Systems, время от времени я получаю такие импульсы)

4b9b3361

Ответ 1

Простое присваивание простым переменным является "атомарным" потоком данных AKA (сложные назначения, такие как += или присваивания элементам или атрибутам объектов, не обязательно, но ваш пример является простым назначением простой, хотя и глобальной, переменной, таким образом, безопасно).

Ответ 2

Составное назначение включает три этапа: read-update-write. Это условие гонки, если выполняется другой поток и записывает новое значение в местоположение после чтения, но перед записью. В этом случае устаревшее значение обновляется и записывается обратно, что будет clobber независимо от того, какое новое значение было написано другим потоком. В Python все, что связано с выполнением одного байтового кода, ДОЛЖНО быть атомарным, но составное присвоение не соответствует этим критериям. Используйте блокировку.

Ответ 3

Руководство по стилю Google советует против этого

Я не утверждаю, что руководства по стилю Google - абсолютная истина, но обоснование в разделе "Потоки" дает некоторое представление (выделение мое):

Не полагайтесь на атомарность встроенных типов.

Хотя встроенные типы данных Pythons, такие как словари, по-видимому, имеют атомарные операции, есть угловые случаи, когда они не являются атомарными (например, если __hash__ или __eq__ реализованы как методы Python), и на их атомарность не следует полагаться. Также не следует полагаться на атомарное присвоение переменных (поскольку это, в свою очередь, зависит от словарей).

Используйте тип данных Queue module Queue в качестве предпочтительного способа передачи данных между потоками. В противном случае используйте модуль потоков и его блокирующие примитивы. Узнайте о правильном использовании условных переменных, чтобы вы могли использовать threading.Condition вместо использования низкоуровневых блокировок.

Итак, моя интерпретация заключается в том, что в Python все похоже на dict, и когда вы делаете a = b в бэкэнде, где-то происходит globals['a'] = b, что плохо, так как dicts не обязательно являются потокобезопасными.

Однако для одной переменной Queue не идеален, поскольку мы хотим, чтобы она содержала только один элемент, и я не смог найти идеальный ранее существующий контейнер в stdlib, который автоматически синхронизирует метод .set(). Так что сейчас я делаю просто:

import threading

myvar = 0
myvar_lock = threading.Lock()
with myvar_lock:
    myvar = 1
with myvar_lock:
    myvar = 2

Интересно, что Мартелли, похоже, не возражает против рекомендации руководства по стилю Google :-) (он работает в Google)

Интересно, имеет ли CPython GIL значение для этого вопроса: что такое глобальная блокировка интерпретатора (GIL) в CPython?

Этот поток также предполагает, что CPython dicts являются потокобезопасными, включая следующую глоссарий, который явно упоминает об этом https://docs.python.org/3/glossary.html#term-global-interpreter-lock

Это упрощает реализацию CPython, делая объектную модель (включая критические встроенные типы, такие как dict) неявно защищенной от одновременного доступа.