2
0
mirror of https://github.com/boostorg/cobalt.git synced 2026-01-19 04:02:16 +00:00
Files
cobalt/doc/design/thread_local.adoc
Klemens Morgenstern 45901641ac renamed to cobalt.
2023-10-16 21:42:07 +08:00

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.