yohhoyの日記

技術的メモをしていきたい日記

C++26 Executionライブラリ(基礎編)

C++2c(C++26)標準ライブラリに追加される実行制御ライブラリ(execution control library)についてメモ。別名:std::execution, Senders/Receivers(S/R)

2025年5月現在、ベースライン提案文書P2300R10までを採択済み。
2025-07-20追記:2025年6月会合にて関連する一連の提案文書P2079R10,
P3149R11, P3284R4, (PDF)P3433R1, P3481R5, P3552R3, P3557R3, P3570R2, (PDF)P3682R0が採択された。

// C++2c
#include <execution>
#include <print>
namespace exec = std::execution;

// タスクチェインを構成
exec::sender auto work =
  exec::just(2, 3)
  | exec::then([](int a, int b) {
      return a * b * 7;
    });
// 呼び出しスレッド上で同期的にタスク実行
auto result = std::this_thread::sync_wait(work);
// 結果値を取り出す
auto [value] = result.value();
// value == int型の値42

CPU並列処理を実現するスレッドプール機構や、コルーチン非同期タスク型といった応用機能は検討進行中GPU/CUDA並列処理のようなベンダ拡張機能では、非同期処理フレームワークに則ったサードパーティ実装が提供される。*2

  • 2025-07-20追記:システムスレッドプールのSchedulerを返すexecution::get_parallel_scheduler関数[P2079R10]と連携してCPU並列処理を行うSenderアダプタexecution::bulkファミリ[P3481R5]、Sender互換コルーチン戻り値型として利用する非同期タスクexecution::task<T>クラス[P3552R3]が採択済み。

実行制御ライブラリ

C++2c実行制御ライブラリでは、非同期(asynchronous)・並行(concurrent)/並列(parallel)処理を統合的に扱うフレームワークを規定する。ライブラリ仕様は非同期処理を実現する枠組みとして記述され、並行/並列処理は具体的なScheduler実装によって実現される。(例:CPUスレッドプール、GPGPU/CUDA処理をサポートするScheduler)

  • <execution>ヘッダ(C++17追加ヘッダを拡張)
  • 基本コンセプト
    • execution::sender
    • execution::receiver
    • execution::operation_state
    • execution::scheduler
  • 基本操作(CPOとして定義)
    • execution::connect:(sender, receiver) -> operation_state
    • execution::start:(operation_state) -> void
    • execution::schedule:(scheduler) -> sender
    • execution::set_value:(receiver, values...) -> void
    • execution::set_error:(receiver, err) -> void
    • execution::set_stopped:(receiver) -> void
  • プロパティ問い合わせ(queries)
    • 標準クエリオブジェクト(CPOとして定義)
    • クエリ可能オブジェクト == Sender属性, Receiver環境, Scheduler
    • クエリ可能オブジェクトの実装ヘルパ型execution::env, execution::prop
  • Senderアルゴリズム(CPOとして定義)→ id:yohhoy:20250612
    • Senderファクトリ:(args...) -> sender
    • Senderアダプタ:(sender, args...) -> sender
    • Senderコンシューマ:(sender) -> result
    • 実行ドメイン == Senderアルゴリズムのカスタマイズ機構
    • Senderアルゴリズムの実装ヘルパ型execution::sender_adaptor_closure
  • C++コルーチンサポート → id:yohhoy:20250613
    • 任意のコルーチンAwaitable型をSenderとして扱える(execution::connectCPO内部動作)
    • Senderをコルーチン内で利用可能にする実装ヘルパ型execution::with_awaitable_senders

基本コンセプト: Sender, Receiver, Operation State

  • 非同期操作(asynchronous operation)
    • 明示的に作成され、1回だけ明示的に開始(start)でき、最終的に3種類いずれかの完了(completion)に到達する。
    • 値完了(value completion):非同期操作の “成功”。0個以上の任意型の値。
    • エラー完了(error completion):非同期操作の “失敗”。1個の任意型のエラー値。
    • 停止完了(stopped completion):非同期操作の “キャンセル”。付帯情報なし。
  • Operation State:execution::operation_state concept
    • 非同期操作に関連付けられる状態オブジェクト。
    • Operation Stateはコピー/ムーブともに不可。*3
    • メンバ型operation_state_conceptでタグ型operation_state_tを宣言。
    • 開始操作execution::startCPOから呼ばれるstartメンバ関数を定義。
  • Sender:execution::sender concept
    • 1つ以上の非同期操作のファクトリ。
    • メンバ型sender_conceptでタグ型sender_tを宣言。
      • 2025-07-20追記:LWG4202にてenable_sender変数テンプレート特殊化を用いて非侵襲的に任意の型をSenderにアダプト可能となる。
    • SenderとReceiverとを接続(connect)してOperation Stateを生成。
    • 接続操作execution::connectCPOから呼ばれるconnectメンバ関数を定義。
  • Receiver:execution::sender concept
    • 非同期操作の値完了/エラー完了/停止完了を受け取るハンドラの集合体。
    • メンバ型receiver_conceptでタグ型receiver_tを宣言。
    • 完了操作execution::set_{value,error,stopped}CPOから呼ばれる完了ハンドラset_{value,error,stopped}メンバ関数を定義。
  • 完了シグネチャ(completion signature) == 完了ハンドラの型情報*4
    • 値完了:execution::set_value_t(Values...)
    • エラー完了:execution::set_error_t(Err)
    • 停止完了:execution::set_stopped_t()
    • 完了シグネチャ集合をexecution::completion_signatures<Sigs...>で表現。
    • Sender/Receiverは少なくとも1個の完了シグネチャに対応する。Sender完了シグネチャ集合 ⊆ Receiver完了シグネチャ集合ならば、両者は接続(connect)可能。*5
    • 2025-08-23追記:P3557R3採択後のSender完了シグネチャ集合の宣言方法は id:yohhoy:20250823 を参照。
exec::sender auto sndr = /* Senderオブジェクト */
exec::receiver auto rcvr = /* Receiverオブジェクト */
// SenderとReceiverを接続(connect)
exec::operation_state auto op = exec::connect(sndr, rcvr);
// 非同期操作の開始(start)
exec::start(op);
// 非同期操作完了時にexec::set_{value,error,stopped}を呼び出し、
// rcvrで定義する完了ハンドラのいずれか1つが呼び出される。
// (Receiverが受け取った結果値の取り出しは別機構で実現する。)

基本コンセプト: Scheduler

  • 実行エージェント(execution agent):タスクを並列実行する機構(例:CPUスレッドstd::threadGPU/CUDAスレッド)。
  • 実行リソース(execution resource):実行エージェントの集合を管理するエンティティ(例:CPUスレッドプール、CUDAスレッド管理)
  • Scheduler:execution::scheduler concept
    • 実行リソース上でタスク実行をスケジュールするためのインタフェースかつ軽量ハンドル。*6
    • メンバ型sender_conceptでタグ型sender_tを宣言。
    • スケジュール操作execution::scheduleCPOから呼ばれるscheduleメンバ関数を定義。
  • 完了Scheduler(completion scheduler)
    • 非同期操作の値完了/エラー完了/停止完了を実行するScheduler。完了種別毎に異なる可能性あり。
  • スケジュールSender(schedule sender)
    • execution::scheduleCPO呼び出しで得られるSender。Scheduler上で空の値を用いて値完了を実行する。
  • execution::run_loop
    • シングルCPUスレッドでのタスクFIFO処理を行う実行リソース。
exec::scheduler auto sch = /* Schedulerオブジェクト */
// スケジュールSenderを作成
exec::sender auto sndr = exec::schedule(sch);

exec::receiver auto rcvr = /* Receiverオブジェクト */
exec::operation_state auto op = exec::connect(sndr, rcvr);
exec::start(op);
// sch上でrcvrに対して値完了rcvr.set_value()が呼ばれる

クエリ可能オブジェクト/クエリオブジェクト

  • クエリ可能オブジェクト(queryable object)
    • 読み取り専用のKey−Valueデータ集合。Key==クエリオブジェクト、Valueは任意型。
    • Senderに関連付けられた属性(attributes)、Receiverに関連付けれた環境(environment)(execution::get_envCPO)
    • Schedulerオブジェクト自身
  • クエリオブジェクト(query object)
    • 問い合わせ(query)操作を行うCPO 兼 クエリ可能オブジェクトの Key。
    • Sender/Receiver/Schedulerに関連付けられたプロパティ(property)値を取得。
  • 標準クエリオブジェクト
    • forwarding_query:Senderアルゴリズム間のクエリ転送可否を問い合わせ。
    • get_allocator:メモリアロケータを問い合わせ。
    • get_stop_token:非同期キャンセル(→id:yohhoy:20250625)のための停止トークンを問い合わせ。
    • execution::get_domain:Sender動作カスタマイズのための実行ドメインを問い合わせ。
    • execution::get_(delegation_)scheduler:Receiverへ関連付けられたSchedulerを問い合わせ。
    • execution::get_forward_progress_guarantee:Schedulerへ前方進行保証を問い合わせ。
    • execution::get_completion_scheduler<completion-tag>:Senderへ完了Schedulerを問い合わせ。
    • execution::get_await_completion_adaptor:Senderへco_awaitオペランド指定時の型変換を問い合わせ。[P3570R2]
  • execution::env:複数のクエリ可能オブジェクトを合成。[P3325R5]
  • execution::prop:単一Key−Valueに対応した最小のクエリ可能オブジェクト。[P3325R5]
exec::receiver auto rcvr = /* Receiverオブジェクト */
// Receiver環境を取得
auto env = exec::get_env(rcvr);
// メモリアロケータを問い合わせ
auto alloc = std::get_allocator(env);


関連URL

*1:サードパーティ・ライブラリの拡張自由度を高めるためにCPOが多用されており、多数のカスタマイゼーションポイントを提供する。代償としてライブラリ仕様記述が複雑化し、難解な仕様となっているのは否めない。

*2:https://github.com/NVIDIA/stdexec

*3:P2300R10, §5.2: "An operation state is neither movable nor copyable"

*4:Receiverメンバ関数として定義する完了ハンドラの関数シグネチャとは異なり、戻り値型を “完了操作CPOの型” に置換した関数型として表現される。例:値完了シグネチャ execution::set_value_t(int) に対応するRcvrクラスメンバ関数は void Rcvr::set_value(int) となる。

*5:ある種類の完了シグネチャには対応しないケースや、同種の完了シグネチャで異なる引数リストに複数対応するケースもある。停止完了シグネチャは引数0個のため、実質 execution::set_stopped_t() の1種類のみ。

*6:P2300R10, §4.2: "A scheduler is a lightweight handle that represents a strategy for scheduling work onto an execution resource."