Является ли назначение переменных Python атомарным?
Скажем, я использую обработчик signal
для обработки таймера интервалов.
def _aHandler(signum, _):
global SomeGlobalVariable
SomeGlobalVariable=True
Могу ли я установить SomeGlobalVariable
, не беспокоясь о том, что в маловероятном сценарии при установке SomeGlobalVariable
(т.е. виртуальная машина Python запускает байт-код для установки переменной), что присваивание в обработчике сигнала может что-то сломать? (т.е. метастабильное состояние)
Обновить. Меня особенно интересует случай, когда "составное присвоение" выполняется вне обработчика.
(возможно, я тоже думаю о "низком уровне", и об этом все позаботятся в Python... исходя из фона Embedded Systems, время от времени я получаю такие импульсы)
Ответы
Ответ 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) неявно защищенной от одновременного доступа.