Приведи пример самой интересной задачи, которую решал.

«Приведи пример самой интересной задачи, которую решал.» — вопрос из категории Софт-скиллы, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый пример ответа, который можно адаптировать под свой опыт.

Ответ

На одном из проектов, связанных с высоконагруженным сервисом обработки финансовых данных, мне нужно было реализовать lock-free очередь для передачи сообщений между потоками. Основная сложность заключалась в обеспечении корректности и производительности при высокой конкуренции на запись и чтение.

Задача: Создать однописатель-многопоточный читатель (SPMC) ring-buffer на C++17 без использования мьютексов.

Решение и ключевые моменты:

template<typename T, size_t Capacity>
class LockFreeSPSCRingBuffer {
    std::atomic<size_t> write_idx_{0};
    std::atomic<size_t> read_idx_{0};
    T buffer_[Capacity];

public:
    bool try_push(const T& item) {
        size_t write_idx = write_idx_.load(std::memory_order_relaxed);
        size_t next_idx = (write_idx + 1) % Capacity;
        // Проверяем, не полна ли очередь, читая индекс чтения
        if (next_idx == read_idx_.load(std::memory_order_acquire)) {
            return false; // Очередь полна
        }
        buffer_[write_idx] = item;
        // Обновляем индекс записи с барьером release, чтобы данные стали видны читателям
        write_idx_.store(next_idx, std::memory_order_release);
        return true;
    }

    bool try_pop(T& item) {
        size_t read_idx = read_idx_.load(std::memory_order_relaxed);
        if (read_idx == write_idx_.load(std::memory_order_acquire)) {
            return false; // Очередь пуста
        }
        item = std::move(buffer_[read_idx]);
        // Обновляем индекс чтения
        read_idx_.store((read_idx + 1) % Capacity, std::memory_order_release);
        return true;
    }
};

Что было интересно:

  1. Глубокое погружение в модель памяти C++: Пришлось тщательно подбирать барьеры памяти (std::memory_order_acquire/release) для гарантии видимости изменений между потоками.
  2. Производительность: Мы сравнивали решение с std::queue + мьютекс и boost::lockfree::spsc_queue. Наша реализация показала сравнимую с boost производительность при 10+ миллионах операций в секунду.
  3. Отладка: Использовали ThreadSanitizer и анализ дампов ядра (core dumps) для поиска тонких гонок данных (data races).
  4. Результат: Очередь стала ключевым компонентом в конвейере обработки, устранив узкое место и снизив задержки на 15%.