← На главную

В C++ предложили лёгкий и тяжёлый барьеры Asymmetric Thread Fences

03.07.2026 17:25 · hackernews

В C++ предложили новый примитив синхронизации — асимметричные барьеры памяти (Asymmetric Thread Fences). Идея простая: если в конкурентном алгоритме один путь выполняется значительно чаще другого, можно повесить лёгкий барьер на частый путь, а всю тяжесть синхронизации переложить на редкий. Пример — Dekker’s algorithm. Обычно на обеих сторонах нужен atomic_thread_fence(memory_order_seq_cst). Но если один из путей — быстрый, а другой — медленный, можно заменить его на asymmetric_thread_fence_light (просто компиляторный барьер) и asymmetric_thread_fence_heavy (реальный аппаратный барьер).

В Folly от Meta уже есть такая реализация. asymmetric_thread_fence_light — это asm volatile("" : : : "memory") (или _ReadWriteBarrier на MSVC). Компилятору запрещают переупорядочивать обращения к памяти, но процессор всё ещё может делать это на лету. А asymmetric_thread_fence_heavy на Linux завязан на системный вызов membarrier() — он дорогой, зато гарантирует полный порядок.

membarrier с флагом MEMBARRIER_CMD_PRIVATE_EXPEDITED заставляет все потоки того же процесса пройти через аппаратный барьер. Перед вызовом процесс должен зарегистрироваться через MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED. Внутри ядро делает smp_mb(), рассылает IPI (межпроцессорные прерывания) всем ядрам, где крутятся потоки процесса, и заставляет их выполнить smp_mb(), потом ещё раз smp_mb(). Это обеспечивает happens-before между операциями до и после барьера. Если в процессе только один поток или одно ядро — membarrier вообще ничего не делает, это fast path.

Формализация для стандарта C++ оказалась нетривиальной. Первая попытка — сделать heavy fence эквивалентным memory_order_seq_cst и установить synchronizes-with между light и heavy — провалилась из-за транзитивности: она запрещала допустимые сценарии на реальном железе. Вторая попытка — заменить synchronizes-with на strong happens-before — разорвала транзитивность, но добавила новые ограничения, которые невозможно реализовать на некоторых архитектурах. Текущий вариант в черновике P1202R0 использует модифицированное определение, которое не требует транзитивности, но даёт корректные гарантии.

Читать оригинал →