В многопроцессной архитектуре (например, между воркерами Gunicorn или Celery) обычные Lock из threading или multiprocessing становятся узким местом из‑за контекстных переключений. Lock‑free структуры на атомарных операциях C11 обходят это. Python позволяет дотянуться до них через
ctypes и _multiprocessing.sharedctypes, работая напрямую с разделяемой памятью без GIL. Частая ошибка: разработчики пишут свой lock‑free код, не учитывая memory ordering и ABA‑проблему.Как собирается атомарный кэш
Берёшь
RawValue и RawArray из _multiprocessing.sharedctypes, выделяешь разделяемую память для флагов, ключей и значений. Для синхронизации используешь C11 __sync_bool_compare_and_swap (CAS) через ctypes.CFUNCTYPE – он вызывает atomic-инструкцию на уровне процессора.from ctypes import c_uint64, c_bool, CFUNCTYPE, POINTER, byref
from _multiprocessing.sharedctypes import RawValue, RawArray
class LockFreeCache:
def __init__(self, capacity=256):
self.capacity = capacity
self.keys = RawArray(c_uint64, capacity)
self.values = RawArray(c_uint64, capacity)
self.flags = RawArray(c_bool, capacity)
self._load_cas()
def _load_cas(self):
libc = ctypes.CDLL(None)
self.cas = CFUNCTYPE(c_bool, POINTER(c_bool), c_bool, c_bool)(
('__sync_bool_compare_and_swap', libc)
)
def set(self, key, value):
idx = hash(key) % self.capacity
while True:
old_flag = c_bool(False)
if self.cas(byref(self.flags[idx]), old_flag, c_bool(True)):
self.keys[idx] = key
self.values[idx] = value
self.flags[idx] = c_bool(False)
return True
Production‑ориентированный пример
Используй такой кэш на hot‑path, где простая блокичка через
multiprocessing.Lock даёт ощутимый оверхед. Например, подсчёт запросов или кэширование результатов между воркерами в асинхронном HTTP‑обработчике. Для прода обязательно добавь управление коллизиями (хэш‑таблица с open addressing), TTL и видимость через memory fence (GCC __sync_synchronize).Trade‑offs и типичные ошибки
Плюсы: без блокировок, масштабируется на многоядерных системах, GIL не мешает. Минусы: ABA‑проблема (например, чтение старой записи после перезаписи), платформозависимость (не на всех архитектурах есть CAS), риск data race при неправильном memory ordering. Предупреждение: не используй этот подход для сложных структур – lock‑free очередь или счётчик проще сделать через
multiprocessing.Value с блокировкой.Вывод: Lock‑free кэш на атомарных операциях оправдан только на узком hot‑path, где блокировка реально давит – в остальных случаях простой Lock надёжнее и читаемее.