Executors and Strands
This section explains execution contexts, executors, and strands.
Code examples assume using namespace boost::capy; is in effect.
|
Execution Context
An execution context owns resources for executing work:
#include <boost/capy/ex/thread_pool.hpp>
thread_pool pool(4); // Context with 4 worker threads
The context manages:
-
Worker threads
-
Work queues
-
Services (see below)
Executors
An executor is a lightweight handle for submitting work to a context:
auto ex = pool.get_executor();
// Submit work
ex.post(handle); // Always queue
ex.dispatch(handle); // Queue or run inline
Executors are cheap to copy and don’t own the context.
Thread Pool
The primary execution context:
#include <boost/capy/ex/thread_pool.hpp>
// Default: hardware_concurrency() threads
thread_pool pool1;
// Explicit thread count
thread_pool pool2(4);
// Single thread (useful for testing)
thread_pool pool3(1);
// Custom thread name prefix
thread_pool pool4(4, "worker-");
Strands
A strand serializes execution—operations don’t run concurrently:
#include <boost/capy/ex/strand.hpp>
thread_pool pool(4);
strand s(pool.get_executor());
// These never run concurrently, even on multi-threaded pool
run_async(s)(task_a());
run_async(s)(task_b());
run_async(s)(task_c());
Why Strands?
Without strands, concurrent access requires locking:
async_mutex mtx;
task<void> modify()
{
auto lock = co_await mtx.lock();
shared_data.modify();
}
With strands, the executor guarantees serialization:
strand s(pool.get_executor());
task<void> modify()
{
// No locking needed—strand serializes
shared_data.modify();
co_return;
}
run_async(s)(modify());
Services
Execution contexts support services—singletons tied to the context lifetime:
#include <boost/capy/ex/execution_context.hpp>
class my_service : public execution_context::service
{
public:
using key_type = my_service;
explicit my_service(execution_context& ctx)
: service(ctx)
{}
void shutdown() override
{
// Called when context is destroyed
}
};
// Usage:
thread_pool pool(4);
pool.make_service<my_service>();
my_service& svc = pool.use_service<my_service>();
Services are destroyed in reverse creation order when the context is destroyed.
Work Tracking
The executor tracks outstanding work:
auto ex = pool.get_executor();
ex.on_work_started(); // Increment
// ... work outstanding ...
ex.on_work_finished(); // Decrement
Use executor_work_guard for RAII:
#include <boost/capy/ex/executor_work_guard.hpp>
{
executor_work_guard guard(ex);
// Work count incremented
} // Work count decremented
run_async handles work tracking automatically.
Choosing the Right Abstraction
| Need | Use |
|---|---|
Run coroutines |
|
Type-erased executor |
|
Serialize access |
|
Explicit locking |
|
Custom executor type |
Implement |
Summary
| Component | Purpose |
|---|---|
|
Execution context with worker threads |
|
Type-erased executor wrapper |
|
Serialize operations |
|
Resume inline if safe, queue otherwise |
|
Always queue |
Services |
Per-context singleton components |
Next Steps
-
Frame Allocators — Optimize coroutine allocation
-
The Executor — Affinity details