tmc::spawn_group()#
spawn_group() provides an imperative interface for building a group of awaitables of the same type. Similar to spawn_many(), it allows you to customize execution behavior and wait for all awaitables to complete, but awaitables are collected incrementally rather than provided all at once.
The wrapped awaitables will be executed concurrently.
Unlike fork_group, spawn_group is lazy - awaitables are not initiated until you co_await the group. This also means spawn_group is movable, so it can be returned from functions or passed around.
Key Differences#
spawn_group vs fork_group:
spawn_groupis lazy - awaitables are collected but not initiated untilco_awaitspawn_groupis movable - can be returned from functions or storedspawn_grouprequires all awaitables to be the same typespawn_groupsupportsrun_on(),resume_on(), andwith_priority()customizations
spawn_group vs spawn_many:
spawn_groupallows imperative construction - add awaitables one at a timespawn_groupcan do HALO (viaadd_clang())spawn_groupcan be reused (viareset())
Template Parameters#
spawn_group() has two template parameters:
MaxCount(default: 0): The maximum number of awaitables that may be added.Awaitable(default:tmc::task<void>): The type of awaitables that will be added. All awaitables must be of this type.
Storage Strategies:
If
MaxCountis non-zero, a fixed-sizestd::array<Awaitable, MaxCount>is used.If
MaxCountis 0, astd::vector<Awaitable>is used, allowing an unlimited number of awaitables.
Result Storage#
Results are returned the same way as spawn_many():
voidif the awaitable result type isvoidstd::array<Result, MaxCount>for fixed-size groupsstd::vector<Result>for dynamic-size groups
If the result type is not default-constructible, each value will be wrapped into std::optional<Result>.
Imperative Add Interface#
.add()#
Adds an awaitable to the group. The awaitable will be initiated when the group is co_awaited.
auto sg = tmc::spawn_group();
sg.add(task_void());
sg.add(another_task());
co_await std::move(sg);
.add_clang()#
Similar to add() but allows the child task’s allocation to be elided via HALO (Heap Allocation eLision Optimization) when compiled with Clang 20+. See HALO for details.
IMPORTANT: You must co_await the result of add_clang() immediately for HALO to be possible.
auto sg = tmc::spawn_group();
co_await sg.add_clang(task_void());
co_await sg.add_clang(another_task());
co_await std::move(sg);
WARNING: Do not use add_clang() in a loop, as Clang will try to reuse the same allocation for multiple active coroutines, causing crashes.
// WRONG - Will crash!
auto sg = tmc::spawn_group();
for (int i = 0; i < 2; i++) {
co_await sg.add_clang(task(i));
}
co_await std::move(sg);
// The equivalent without the loop works fine.
auto sg = tmc::spawn_group();
co_await sg.add_clang(task(0));
co_await sg.add_clang(task(1));
co_await std::move(sg);
// Or you can use regular add() in a loop
// (but HALO will not be performed)
auto sg = tmc::spawn_group();
for (int i = 0; i < 2; i++) {
sg.add(task(i));
}
co_await std::move(sg);
Awaitable Customizations#
The spawn_group awaitable supports these Awaitable Customizations:
run_on(),
resume_on(),
with_priority(),
co_await,
fork()
Unlike fork_group, spawn_group requires you to customize the executor and priority for all tasks in the group at once:
auto sg = tmc::spawn_group();
sg.add(task1());
sg.add(task2());
co_await std::move(sg).run_on(tmc::cpu_executor()).with_priority(1);
// Or fork them
auto forked = std::move(sg).run_on(tmc::cpu_executor()).fork();
do_some_work();
co_await std::move(forked);
Movability#
Unlike fork_group, spawn_group is movable. This makes it useful for building task groups in helper functions:
auto build_tasks() -> tmc::aw_spawn_group<0, tmc::task<int>> {
auto sg = tmc::spawn_group<0, tmc::task<int>>();
sg.add(compute_task(1));
sg.add(compute_task(2));
return sg; // OK - spawn_group is movable
}
tmc::task<void> example() {
auto sg = build_tasks();
auto results = co_await std::move(sg);
}
API Reference#
-
template<size_t MaxCount = 0, typename Awaitable = tmc::task<void>>
aw_spawn_group<MaxCount, std::remove_cvref_t<Awaitable>> tmc::spawn_group()# Constructs an empty spawn group with default template parameters.
-
template<size_t MaxCount = 0, typename Awaitable>
aw_spawn_group<MaxCount, std::remove_cvref_t<Awaitable>> tmc::spawn_group(Awaitable &&Aw)# Constructs a spawn group with the first awaitable, deducing the awaitable type from the argument.
MaxCountis the maximum number of awaitables that will be added.If
MaxCountis non-zero, a fixed-sizestd::arraywill be allocated.If
MaxCountis zero, astd::vectorwill be used, allowing an unlimited number of awaitables to be added.
Awaitableis automatically deduced from the argument.
-
template<size_t MaxCount, typename Awaitable>
class aw_spawn_group : public tmc::detail::run_on_mixin<aw_spawn_group<MaxCount, Awaitable>>, public tmc::detail::resume_on_mixin<aw_spawn_group<MaxCount, Awaitable>>, public tmc::detail::with_priority_mixin<aw_spawn_group<MaxCount, Awaitable>># Similar to
tmc::spawn_many(), but allows for imperative construction of the task group. Awaitables will be collected, but not initiated until you co_await this. Unliketmc::fork_group, this type is movable, so it can be returned from functions or passed around.Awaitableis the type of awaitable that will be added to the group. All awaitables added to the group must be of the same type.MaxCountis the maximum number of awaitables that may be added.If
MaxCountis non-zero, a fixed-sizestd::arraywill be used. You can add up to this many awaitables to the group. If less than MaxCount awaitables are added, the remaining results in the result array will be default-initialized.If
MaxCountis zero, an unlimited number of awaitables can be added. The results will be returned in a right-sizedstd::vector.
Public Functions
-
inline aw_spawn_group()#
Constructs an empty spawn group. It is recommended to use
tmc::spawn_group()instead of this constructor.
-
inline aw_spawn_group(Awaitable &&Aw)#
Constructs an empty spawn group. It is recommended to use
tmc::spawn_group(Awaitable&& Aw)instead of this constructor.
-
inline void add(Awaitable &&Aw)#
Adds an awaitable to the group. The awaitable will be initiated when the group is co_awaited.
-
inline aw_spawn_group_add_clang add_clang(Awaitable &&Aw)#
Similar to
add()but allows the child task’s allocation to be elided by combining it into the parent’s allocation (HALO). This works by using specific attributes that are only available on Clang 20+. You can safely call this function on other compilers, but no HALO-specific optimizations will be applied.WARNING: Don’t allow coroutines passed into this to cross a loop boundary, or Clang will try to reuse the same allocation for multiple active coroutines.
// the following usage will make your program CRASH! auto sg = tmc::spawn_group(); for (int i = 0; i < 2; i++) { co_await sg.add_clang(task(i)); } co_await std::move(sg);IMPORTANT: This returns a dummy awaitable. For HALO to work, you should not store the dummy awaitable. Instead,
co_awaitthis expression immediately. Proper usage:// note that this is the same as the prior example // but without the loop, it works fine auto sg = tmc::spawn_group(); co_await sg.add_clang(task(0)); co_await sg.add_clang(task(1)); co_await std::move(sg);
-
inline aw_spawn_many_impl<Result, MaxCount, false, false> operator co_await() && noexcept#
Initiates all of the wrapped awaitables and waits for them to complete.
-
inline aw_spawn_many_fork<Result, MaxCount, false> fork() && noexcept#
Initiates all of the wrapped awaitables, without suspending the current coroutine. You must join them by awaiting the returned awaitable before it goes out of scope.
-
inline void reset() noexcept#
After you
co_awaitthis group, you may callreset()to make it usable again. This allows you to accumulate andco_awaitanother group of awaitables.
-
inline size_t capacity() noexcept#
Returns the maximum capacity of the spawn_group as determined by the MaxCount parameter.
If the capacity is unlimited, this will return
std::numeric_limits<size_t>::max(), i.e.static_cast<size_t>(-1).
-
class aw_spawn_group_add_clang : private tmc::detail::AwaitTagNoGroupAsIs#
This is a dummy awaitable. Don’t store this in a variable. For HALO to work, you must
co_await sg.add_clang()immediately.