Я недавно написал программу, в которой использовался простой шаблон производитель / потребитель. Изначально в ней была ошибка, связанная с неправильным использованием потоков. Блокировка, которую я в итоге исправил. Но это заставило меня задуматься о том, можно ли реализовать шаблон производителя / потребителя без блокировки.
Требования в моем случае были просты:
- Одна ветка производителя.
- Одна потребительская нить.
- В очереди есть место только для одного элемента.
- Производитель может произвести следующий предмет до того, как будет использован текущий. Текущий предмет поэтому потерян, но для меня это нормально.
- Потребитель может потреблять текущий предмет до того, как будет произведен следующий. Поэтому текущий предмет потребляется дважды (или больше), но для меня это нормально.
Итак, я написал это:
QUEUE_ITEM = None
# this is executed in one threading.Thread object
def producer():
global QUEUE_ITEM
while True:
i = produce_item()
QUEUE_ITEM = i
# this is executed in another threading.Thread object
def consumer():
global QUEUE_ITEM
while True:
i = QUEUE_ITEM
consume_item(i)
Мой вопрос: является ли этот код потокобезопасным?
Немедленный комментарий: этот код на самом деле не без блокировки - я использую CPython и у него есть GIL.
Я немного протестировал код, и, похоже, он работает. Это переводит к некоторым операциям LOAD и STORE, которые являются атомарными из-за GIL. Но я также знаю, что операция del x
не является атомарной, когда x реализует метод __del__
. Поэтому, если у моего предмета есть метод __del__
и произойдет какое-то неприятное планирование, вещи могут сломаться. Или нет?
Другой вопрос: какие ограничения (например, для типа производимых элементов) я должен наложить, чтобы вышеописанный код работал нормально?
Мои вопросы касаются только теоретической возможности использовать причуды CPython и GIL, чтобы найти решение без блокировки (то есть без блокировок, таких как threading.Lock явно в коде).