mirror of
https://github.com/boostorg/cobalt.git
synced 2026-01-19 04:02:16 +00:00
84 lines
3.6 KiB
Plaintext
84 lines
3.6 KiB
Plaintext
== Executors
|
|
|
|
Since everything is asynchronous the library needs to use an event-loop.
|
|
Because everything is single-threaded, it can be assumed that there is exactly one executor
|
|
per thread, which will suffice for 97% of use-cases.
|
|
Therefore, there is a `thread_local` executor that gets used as default
|
|
by the coroutine objects (although stored by copy in the coroutine promise).
|
|
|
|
Likewise, there is one `executor` type used by the library,
|
|
which defaults to `asio::any_io_executor`.
|
|
|
|
NOTE: If you write your own coroutine, it should hold a copy of the executor,
|
|
and have a `get_executor` function returning it by const reference.
|
|
|
|
=== Using Strands
|
|
|
|
While strands can be used, they are not compatible with the `thread_local` executor.
|
|
This is because they might switch threads, thus they can't be `thread_local`.
|
|
|
|
If you wish to use strands (e.g. through a <<spawn, spawn>>)
|
|
the executor for any <<promise, promise>>, <<generator, generator>> or <<channel, channel>>
|
|
must be assigned manually.
|
|
|
|
In the case of a <<channel, channel>> this is a constructor argument,
|
|
but for the other coroutine types, `asio::executor_arg` needs to be used.
|
|
This is done by having `asio::executor_arg_t` (somewhere) in the argument
|
|
list directly followed by the executor to be used in the argument list of the coroutine, e.g.:
|
|
|
|
[source,cpp]
|
|
----
|
|
cobalt::promise<void> example_with_executor(int some_arg, asio::executor_arg_t, cobalt::executor);
|
|
----
|
|
|
|
This way the coroutine-promise can pick up the executor from the third argument,
|
|
instead of defaulting to the thread_local one.
|
|
|
|
The arguments can of course be defaulted, to make them less inconvenient,
|
|
if they are sometimes with a thread_local executor.
|
|
|
|
[source,cpp]
|
|
----
|
|
cobalt::promise<void> example_with_executor(int some_arg,
|
|
asio::executor_arg_t = asio::executor_arg,
|
|
cobalt::executor = cobalt::this_thread::get_executor());
|
|
----
|
|
|
|
If this gets omitted on a strand an exception of type `asio::bad_allocator` is thrown,
|
|
or - worse - the wrong executor is used.
|
|
|
|
== polymorphic memory resource
|
|
|
|
Similarly, the library uses a thread_local `pmr::memory_resource` to allocate
|
|
coroutine frames & to use as allocator on asynchronous operations.
|
|
|
|
|
|
|
|
The reason is, that users may want to customize allocations,
|
|
e.g. to avoid locks, limit memory usage or monitor usage.
|
|
`pmr` allows us to achieve this without introducing unnecessary template parameters,
|
|
i.e. no `promise<T, Allocator>` complexity.
|
|
Using `pmr` however does introduce some minimal overheads,
|
|
so a user has the option to disable by defining `BOOST_COBALT_NO_PMR`.
|
|
|
|
<<op, op>> uses an internal resource optimized for asio's allocator usages
|
|
and <<gather, gather>>, <<race,race>> and <<join,join>> use a monotonic resource to miminize allocations.
|
|
Both still work with `BOOST_COBALT_NO_PMR` defined, in which case they'll use `new/delete` as upstream allocations.
|
|
|
|
<<main,main>> and <<thread,thread>> single `pmr::unsynchronized_pool_resource` per thread with PMR enabled.
|
|
|
|
NOTE: If you write your own coroutine, it should have a get_allocator function
|
|
returning a `pmr::polymorphic_allocator<void>`.
|
|
|
|
== cancellation
|
|
|
|
cobalt uses implicit cancellation based on `asio::cancellation_signal`.
|
|
This is mostly used implicitly (e.g. with <<race, race>>),
|
|
so that there is very little explicit use in the examples.
|
|
|
|
NOTE: If you write custom coroutine it must return a `cancellation_slot` from a
|
|
`get_cancellation_slot` function in order to be able to cancel other operations.
|
|
|
|
NOTE: If you write a custom awaitable, it can use that function in await_suspend to receive cancellation signals.
|
|
|