Перейти к содержанию

Последнее обновление:2026-10-12

Практика с channel в C++20

Практика C++20: channel

Продолжая два предыдущих прямых эфира, где разбирались применение и внутренняя реализация thread, jthread, stop_token, stop_source, stop_callback, cv, cv_any и других, — как на основе этих знаний реализовать небольшой проект?

Так родилась эта статья: написать свой channel.

1.Дизайн

1.1 Проектирование интерфейса

На уровне интерфейса мы хотим, чтобы он был как у обычного channel: чтобы можно было за один раз создать channel для отправки и получения.

Например:

auto [sender, receiver] = make_channel<int>();

Реализовать это можно, предоставив по аналогии с make_unique внешний интерфейс; возвращаемое значение получается через структурированные привязки C++17 — так мы получаем два channel.

Два ключевых интерфейса: отправка и получение.

  • Отправка
 sender.send(counter++);
  • Получение
auto value = receiver.recv();

Помимо этого, можно предоставить интерфейс shutdown, вызываемый в деструкторе, чтобы при блокировке send и recv их можно было разбудить и освободить.

Например:

bool shutdown() {
    if (!state_) {
        return false;
    }

    light_city_lock lk(state_->mutex);
    if (source_.stop_requested()) {
        return false;
    }
    source_.request_stop();
    state_->cv.notify_all();
    return true;
}

1.2 Проектирование внутренней очереди

Внутреннюю очередь можно спроектировать по аналогии с опубликованным несколькими днями ранее потокобезопасным чередом многопоточных производителей и потребителей: внутри можно хранить state, включающий queue, mutex и cv_any.

struct State {
    std::queue<T> queue;
    std::mutex mutex;
    // C++11 condition_variable_any
    std::condition_variable_any cv;
};

2.Использование

Использовать очень просто: достаточно создать make'ом два channel, а затем вызывать send и recv.

Здесь я использовал jthread + stop_token для этой работы, например:

std::jthread sender_thread(
    [](Channel<int> sender, std::stop_token token) {
      int counter = 0;
      while (!token.stop_requested()) {
        sender.send(counter++);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
      }
    },
    std::move(sender), token);

Принимающий поток устроен аналогично; когда выполнение почти завершено, мы можем вызвать

stop_source.request_stop();

Так мы смоделировали channel: отправляющая сторона посылает данные, принимающая — получает их.

Пример запуска:

➜  channel ./a.out                  
Received: 0
Received: 1
Received: 2
Received: 3
Received: 4
Received: 5
Received: 6
Received: 7
Received: 8
Received: 9

Похожие темы

Комментарии