синхронизация потоков python
Одним из важных моментов при использовании потоков является избежание конфликтов доступа, когда более одному потоку требуется доступ к переменной или какому-либо ресурсу. Если не проявить осторожность, одновременный доступ и модификация из нескольких потоков может вызвать всевозможные проблемы, и, что ещё хуже, эти проблемы имеют свойство проявляться лишь под большой нагрузкой, или на продакшн-серверах, или на более быстром железе, которое используют ваши клиенты. (Сейчас более-менее устоялось название "heisenbugs" для трудновоспроизводимых багов - прим. перев.)
Для примера представьте программу на Python, обрабатывающую какие-нибудь данные и отслеживающую, сколько элементов было обработано:
counter = 0 def process_item(item): global counter ... do something with item ... counter += 1
Если вызывать эту функцию из более чем одного потока, можно обнаружить, что счетчик counter не обязательно точен. Код будет работать верно в большинстве случаев, но иногда пропускать один или несколько элементов. Причина в том, что операция сложения на самом деле выполняется в три шага: интерпретатор получает текущее значение счетчика, вычисляет новое значение, и наконец, записывает его в переменную.
Если другой поток получает управление в тот момент, когда текущий поток получил значение переменной, он может выполнить вышеописанные 3 шага до того, как их выполнит текущий поток. А так как оба потока получили одно и то же начальное значение переменной, то переменная будет увеличена лишь на 1.
Другая распространённая проблема заключается в возникновении объектов в неполном или неконсистентном состоянии. Объект в таком состоянии может возникнуть, когда поток, производящий инициализацию или обновление сложной структуры данных прерывается другим потоком, который пытается эту структуру использовать во время обновления или инициализации.
Атомарные операции
Простейший способ синхронизировать доступ к разделяемым данным или ресурсам - положиться на атомарные операции в интерпретаторе. Атомарная операция - операция, выполняемая за 1 шаг, без возможности прерывания её другим потоком.
В целом, этот подход работает только если разделяемый ресурс состоит из единственного экземпляра встроенного типа, например, строки, числа, списка или словаря. Вот некоторые потокобезопасные операции: