コンテンツにスキップ

最終更新:2026-10-12

C++20 channel 実践

C++20 実践:channel

前回と前々回の配信で、thread、jthread、stop_token、stop_source、stop_callback、cv、cv_any などの使い方と低レベル実装を解説しました。では、これらの知識を基に小さなプロジェクトを実装するにはどうすればよいでしょうか?

そこで本記事では、channel を1つ書いてみることにしました。

1.設計

1.1 インターフェース設計

インターフェース面では、汎用の channel と同じように、送信・受信の channel を一度に作れることを期待します。

例えば:

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

この実装は make_unique と同様に、外部に公開するインターフェースを提供し、戻り値は C++17 の構造化束縛で受け取ります。これで2つの channel が得られます。

最も核心的な2つのインターフェース:送信と受信です。

  • 送信
 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 で2つの 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

関連トピック

コメント