🧠 C++ хитрая и интересная задача (lock-free)
Задача: реализуй однопоточный производитель / однопоточный потребитель (SPSC) кольцевой буфер без мьютексов — только на
Требования:
-
- Ёмкость — степень двойки; индексация — маской.
- Гарантируется ровно один поток-производитель и ровно один поток-потребитель.
Скелет:
💡 Подсказки:
- Пиши в buf[tail & mask], затем публикуй запись:
tail.store(next, std::memory_order_release);
- Читай из buf[head & mask] после проверки наличия данных, затем:
head.store(next, std::memory_order_release);
- На чтение границ используйте std::memory_order_acquire:
- Producer: сначала head.load(memory_order_acquire) (чтобы не переписать не потреблённое).
- Consumer: сначала tail.load(memory_order_acquire) (чтобы увидеть свежие записи).
- Ни одного fetch_add не нужно: вычисляй next = idx + 1.
- Проверка переполнения: буфер полон, если next_tail == head.
- Чтобы избежать ложного шаринга — разнеси head, tail и buf (см. alignas(64)).
- Не забудь про TriviallyCopyable/Noexcept: для общего T лучше использовать std::is_nothrow_copy_assignable_v<T> и/или перемещение.
🎯 Бонус-тест
В двух потоках гоняй счётчик 0..1e7 через буфер.
На выходе проверь, что последовательность непрерывна и без пропусков.
Задача: реализуй однопоточный производитель / однопоточный потребитель (SPSC) кольцевой буфер без мьютексов — только на
std::atomic
и правильных порядках памяти.Требования:
-
push(const T&)
и pop(T&)
— O(1)
, без блокировок; возвращают false
, если буфер полон/пуст.- Ёмкость — степень двойки; индексация — маской.
- Гарантируется ровно один поток-производитель и ровно один поток-потребитель.
Скелет:
#include <atomic>
#include <array>
#include <cstddef>
#include <optional>
template<typename T, std::size_t N>
class SpscRing {
static_assert((N & (N - 1)) == 0, "N must be power of two");
public:
bool push(const T& v) {
// твой код
return false;
}
bool pop(T& out) {
// твой код
return false;
}
private:
alignas(64) std::atomic<size_t> head{0}; // consumer reads
alignas(64) std::atomic<size_t> tail{0}; // producer writes
alignas(64) std::array<T, N> buf{};
static constexpr size_t mask = N - 1;
};
💡 Подсказки:
- Пиши в buf[tail & mask], затем публикуй запись:
tail.store(next, std::memory_order_release);
- Читай из buf[head & mask] после проверки наличия данных, затем:
head.store(next, std::memory_order_release);
- На чтение границ используйте std::memory_order_acquire:
- Producer: сначала head.load(memory_order_acquire) (чтобы не переписать не потреблённое).
- Consumer: сначала tail.load(memory_order_acquire) (чтобы увидеть свежие записи).
- Ни одного fetch_add не нужно: вычисляй next = idx + 1.
- Проверка переполнения: буфер полон, если next_tail == head.
- Чтобы избежать ложного шаринга — разнеси head, tail и buf (см. alignas(64)).
- Не забудь про TriviallyCopyable/Noexcept: для общего T лучше использовать std::is_nothrow_copy_assignable_v<T> и/или перемещение.
🎯 Бонус-тест
В двух потоках гоняй счётчик 0..1e7 через буфер.
На выходе проверь, что последовательность непрерывна и без пропусков.
❤6🔥3🥰1