Является ли операция изменения счетчика ссылок атомарной в Python?
Что проверяют
Понимаете ли вы, что делает GIL и на каком уровне вообще ставится вопрос об атомарности. Тема любимая: правильный ответ требует развести уровень интерпретатора и уровень вашего Python-кода, а неправильный звучит гладко.
Как отвечать
Сами по себе увеличение и уменьшение счётчика ссылок это обычные операции над полем в структуре объекта, без блокировок и без атомарных инструкций процессора. Безопасными их делает GIL: трогать счётчик может только поток, который держит глобальную блокировку, поэтому из Python-кода испортить его нельзя. Это одна из причин, по которым GIL вообще существует.
Верная формулировка звучит так: операция не атомарна на уровне процессора, но сериализована глобальной блокировкой, и для прикладного кода это неразличимо.
Дальше стоит перечислить оговорки, за них и добавляют балл:
- расширения на C, отпускающие GIL, обязаны не трогать объекты Python в это время, иначе счётчик поедет и интерпретатор упадёт;
- в экспериментальной сборке без GIL счётчики стали атомарными, часть объектов сделали бессмертными, а часть учёта перевели на схему со смещением к потоку-владельцу, и всё это стоит производительности;
- счётчик не спасает от циклов ссылок, их разбирает сборщик из модуля
gc.
И главное: неатомарным в Python остаётся не счётчик, а ваш код. counter += 1 это чтение, сложение и запись, между которыми поток может быть вытеснен, поэтому общий счётчик защищают threading.Lock или заменяют на queue.Queue.
Где спотыкаются
Говорят, что GIL не гарантирует целостность счётчиков, и делают вывод, что подсчёт ссылок надо синхронизировать вручную. Из Python это в принципе невозможно и не нужно.
Из фразы «GIL защищает объекты» выводят, что многопоточный код на Python потокобезопасен сам по себе. Защищены внутренние структуры интерпретатора, а не ваши инварианты.
Опираются на то, что list.append атомарен, и строят на этом логику. Отдельные операции действительно укладываются в один вызов под блокировкой, но их список нигде не зафиксирован, меняется вместе с реализацией и рассыпается, как только шагов становится два.