1*03ce13f7SAndroid Build Coastguard Worker# `marl::Scheduler` 2*03ce13f7SAndroid Build Coastguard Worker 3*03ce13f7SAndroid Build Coastguard WorkerTable of Contents: 4*03ce13f7SAndroid Build Coastguard Worker 5*03ce13f7SAndroid Build Coastguard Worker- [`marl::Scheduler`](#marlscheduler) 6*03ce13f7SAndroid Build Coastguard Worker - [Binding](#binding) 7*03ce13f7SAndroid Build Coastguard Worker - [Fibers](#fibers) 8*03ce13f7SAndroid Build Coastguard Worker - [Tasks](#tasks) 9*03ce13f7SAndroid Build Coastguard Worker - [Workers](#workers) 10*03ce13f7SAndroid Build Coastguard Worker - [`marl::Scheduler::Worker::run()`](#marlschedulerworkerrun) 11*03ce13f7SAndroid Build Coastguard Worker - [`marl::Scheduler::Worker::runUntilIdle()`](#marlschedulerworkerrununtilidle) 12*03ce13f7SAndroid Build Coastguard Worker - [`marl::Scheduler::Worker::waitForWork()`](#marlschedulerworkerwaitforwork) 13*03ce13f7SAndroid Build Coastguard Worker - [`marl::Scheduler::Worker::spinForWork()`](#marlschedulerworkerspinforwork) 14*03ce13f7SAndroid Build Coastguard Worker - [`marl::Scheduler::Worker::suspend()`](#marlschedulerworkersuspend) 15*03ce13f7SAndroid Build Coastguard Worker - [Worker Types](#worker-types) 16*03ce13f7SAndroid Build Coastguard Worker - [Single-Threaded-Workers](#single-threaded-workers) 17*03ce13f7SAndroid Build Coastguard Worker - [Multi-Threaded-Workers](#multi-threaded-workers) 18*03ce13f7SAndroid Build Coastguard Worker 19*03ce13f7SAndroid Build Coastguard WorkerThe `marl::Scheduler` is the most complex part of marl and is responsible for executing tasks and keeping threads running when tasks become blocked. 20*03ce13f7SAndroid Build Coastguard Worker 21*03ce13f7SAndroid Build Coastguard WorkerThis document describes the inner workings of the scheduler. This document is not intended to describe usage. 22*03ce13f7SAndroid Build Coastguard Worker 23*03ce13f7SAndroid Build Coastguard Worker## Binding 24*03ce13f7SAndroid Build Coastguard Worker 25*03ce13f7SAndroid Build Coastguard WorkerThe scheduler must be bound to each thread that calls `marl::schedule()`. 26*03ce13f7SAndroid Build Coastguard WorkerThe scheduler destructor will block until the scheduler is unbound from all threads. 27*03ce13f7SAndroid Build Coastguard Worker 28*03ce13f7SAndroid Build Coastguard WorkerBinding is made using the `marl::Scheduler::bind()` and `marl::Scheduler::unbind()` methods. 29*03ce13f7SAndroid Build Coastguard Worker 30*03ce13f7SAndroid Build Coastguard WorkerBinding assigns a thread-local storage variable so the scheduler is associated with the given thread. This serves two purposes: 31*03ce13f7SAndroid Build Coastguard Worker 32*03ce13f7SAndroid Build Coastguard Worker1. It allows `marl::schedule()` and the various synchronization primitives to be called without requiring a pointer to the `marl::Scheduler`. 33*03ce13f7SAndroid Build Coastguard Worker2. More importantly, it provides a way to get the currently executing fiber for the current thread. This is used by `marl::ConditionVariable::wait()` to suspend the current fiber and place it into a vector so the `marl::ConditionVariable::notify_`xxx`()` methods can reschedule the blocked fibers. 34*03ce13f7SAndroid Build Coastguard Worker 35*03ce13f7SAndroid Build Coastguard WorkerEach binding also creates an internal [Single-Threaded-Worker](#single-threaded-workers) for the calling thread. This worker is used for scheduling tasks when there are no [Multi-Threaded-Workers](#multi-threaded-workers) available. Unbinding will ensure that all scheduled tasks for the [Single-Threaded-Worker](#single-threaded-workers) are completed before returning. 36*03ce13f7SAndroid Build Coastguard Worker 37*03ce13f7SAndroid Build Coastguard Worker## Fibers 38*03ce13f7SAndroid Build Coastguard Worker 39*03ce13f7SAndroid Build Coastguard WorkerA [fiber](https://en.wikipedia.org/wiki/Fiber_(computer_science)) is a lightweight cooperative thread, which can be suspended and resumed at explicit yield points. 40*03ce13f7SAndroid Build Coastguard Worker 41*03ce13f7SAndroid Build Coastguard WorkerAt the time of writing, there's no standard and cross-platform library for fibers or coroutines, so marl implements the `marl::OSFiber` class for each supported platform and ABI. Most of these implementations are written in assembly and simply save and restore the callee-saved registers along with maintaining an allocation for the fiber's stack. `marl::OSFiber` is an internal implementation detail, and is not exposed in the public API. 42*03ce13f7SAndroid Build Coastguard Worker 43*03ce13f7SAndroid Build Coastguard Worker`marl::Scheduler::Fiber` is the public fiber interface that is tightly coupled with the `marl::Scheduler`. The `marl::Scheduler::Fiber` has a simple `std::condition_variable` like interface. 44*03ce13f7SAndroid Build Coastguard Worker 45*03ce13f7SAndroid Build Coastguard WorkerEach `marl::Scheduler::Fiber` is permanently associated with a `marl::Scheduler::Worker`, and is guaranteed to only ever be resumed on the same thread used to suspend. 46*03ce13f7SAndroid Build Coastguard Worker 47*03ce13f7SAndroid Build Coastguard Worker## Tasks 48*03ce13f7SAndroid Build Coastguard Worker 49*03ce13f7SAndroid Build Coastguard WorkerA `marl::Task` is an alias to `std::function<void()>`, a function that takes no arguments, and returns no value. 50*03ce13f7SAndroid Build Coastguard Worker 51*03ce13f7SAndroid Build Coastguard WorkerTasks are scheduled using `marl::schedule()`, and are typically implemented as a lambda: 52*03ce13f7SAndroid Build Coastguard Worker 53*03ce13f7SAndroid Build Coastguard Worker```c++ 54*03ce13f7SAndroid Build Coastguard Workermarl::schedule([] { 55*03ce13f7SAndroid Build Coastguard Worker printf("Hello world!\n"); 56*03ce13f7SAndroid Build Coastguard Worker}); 57*03ce13f7SAndroid Build Coastguard Worker``` 58*03ce13f7SAndroid Build Coastguard Worker 59*03ce13f7SAndroid Build Coastguard WorkerWhile the `marl::Task` signature takes no parameters, it is common to capture variables as part of this lambda for task inputs and outputs. 60*03ce13f7SAndroid Build Coastguard Worker 61*03ce13f7SAndroid Build Coastguard WorkerAll the marl synchronization primitives (with exception of `marl::ConditionVariable`) hold a shared pointer to internal state, and you are encouraged to capture these **by value**. This may seem counter-intuitive, but passing by reference can lead to memory corruption if the task outlives the stack used to call `marl::schedule()`. Maintaining a shared state object clearly has allocation and performance overheads, but it was decided that the safety outweighed the costs. 62*03ce13f7SAndroid Build Coastguard Worker 63*03ce13f7SAndroid Build Coastguard Worker```c++ 64*03ce13f7SAndroid Build Coastguard Workermarl::WaitGroup wg(1); 65*03ce13f7SAndroid Build Coastguard Workermarl::schedule([=] { // capture by value, not reference! 66*03ce13f7SAndroid Build Coastguard Worker printf("Hello world!\n"); 67*03ce13f7SAndroid Build Coastguard Worker wg.done(); 68*03ce13f7SAndroid Build Coastguard Worker}); 69*03ce13f7SAndroid Build Coastguard Workerwg.wait(); 70*03ce13f7SAndroid Build Coastguard Worker``` 71*03ce13f7SAndroid Build Coastguard Worker 72*03ce13f7SAndroid Build Coastguard Worker## Workers 73*03ce13f7SAndroid Build Coastguard Worker 74*03ce13f7SAndroid Build Coastguard WorkerThe scheduler holds a number of `marl::Scheduler::Worker`s. Each worker holds: 75*03ce13f7SAndroid Build Coastguard Worker 76*03ce13f7SAndroid Build Coastguard Worker- `work.tasks` - A queue of tasks, yet to be started. 77*03ce13f7SAndroid Build Coastguard Worker- `work.fibers` - A queue of suspended fibers, ready to be resumed. 78*03ce13f7SAndroid Build Coastguard Worker- `work.waiting` - A queue of suspended fibers, waiting to be resumed or time out. 79*03ce13f7SAndroid Build Coastguard Worker- `work.num` - A counter that is kept in sync with `work.tasks.size() + work.fibers.size()`. 80*03ce13f7SAndroid Build Coastguard Worker- `work.numBlockedFibers` - A counter that records the current number of fibers blocked in a [`suspend()`](#marlschedulerworkersuspend) call. 81*03ce13f7SAndroid Build Coastguard Worker- `idleFibers` - A set of idle fibers, ready to be reused. 82*03ce13f7SAndroid Build Coastguard Worker 83*03ce13f7SAndroid Build Coastguard WorkerWhen a task is scheduled with a call to `marl::schedule()`, a worker is picked, and the task is placed on to the worker's `work.tasks` queue. The worker is picked using the following rules: 84*03ce13f7SAndroid Build Coastguard Worker 85*03ce13f7SAndroid Build Coastguard Worker- If the scheduler has no dedicated worker threads (`marl::Scheduler::config().workerThreads.count == 0`), then the task is queued on to the [Single-Threaded-Worker](#single-threaded-workers) for the currently executing thread. 86*03ce13f7SAndroid Build Coastguard Worker- Otherwise one of the [Multi-Threaded-Workers](#multi-threaded-workers) is picked. If any workers have entered a [spin-for-work](#marlschedulerworkerspinforwork) state, then these will be prioritized, otherwise a [Multi-Threaded-Worker](#multi-threaded-workers) is picked in a round-robin fashion. 87*03ce13f7SAndroid Build Coastguard Worker 88*03ce13f7SAndroid Build Coastguard Worker### `marl::Scheduler::Worker::run()` 89*03ce13f7SAndroid Build Coastguard Worker 90*03ce13f7SAndroid Build Coastguard Worker`run()` is the main processing function for worker fibers. `run()` is called by the start of each [Multi-Threaded-Worker](#multi-threaded-workers) thread, and whenever a new worker fiber is spawned from [`Worker::suspend()`](#marlschedulerworkersuspend) when all other fibers have become blocked. 91*03ce13f7SAndroid Build Coastguard Worker 92*03ce13f7SAndroid Build Coastguard WorkerThis function is shared by both the [Single-Threaded](#single-threaded-workers) and [Multi-Threaded](#multi-threaded-workers) worker types. 93*03ce13f7SAndroid Build Coastguard Worker 94*03ce13f7SAndroid Build Coastguard Worker`run()` calls `runUntilShutdown()`, which will enter a loop that: 95*03ce13f7SAndroid Build Coastguard Worker 96*03ce13f7SAndroid Build Coastguard Worker- Calls [`waitForWork()`](#marlschedulerworkerwaitforwork) to block until there's something new to process. 97*03ce13f7SAndroid Build Coastguard Worker- Calls [`runUntilIdle()`](#marlschedulerworkerrununtilidle) to process all new tasks and fibers. Note that fibers can switch inside [`runUntilIdle()`](#marlschedulerworkerrununtilidle), so the execution of `run()` may hop between fibers for a single thread. 98*03ce13f7SAndroid Build Coastguard Worker 99*03ce13f7SAndroid Build Coastguard WorkerThis loop continues until the worker has finished all its work and has been told to shut down. 100*03ce13f7SAndroid Build Coastguard Worker 101*03ce13f7SAndroid Build Coastguard WorkerOnce the loop has exited due to the worker being told to shut down, the `mainFiber` is resumed, which will handle the rest of the shutdown logic. 102*03ce13f7SAndroid Build Coastguard Worker 103*03ce13f7SAndroid Build Coastguard Worker 104*03ce13f7SAndroid Build Coastguard Worker 105*03ce13f7SAndroid Build Coastguard Worker### `marl::Scheduler::Worker::runUntilIdle()` 106*03ce13f7SAndroid Build Coastguard Worker 107*03ce13f7SAndroid Build Coastguard WorkerAs the name suggests, this function executes its work until there is no more work, or all work is blocked. 108*03ce13f7SAndroid Build Coastguard Worker 109*03ce13f7SAndroid Build Coastguard WorkerThe basic logic of this method is as follows: 110*03ce13f7SAndroid Build Coastguard Worker 111*03ce13f7SAndroid Build Coastguard Worker1. Resume any unblocked tasks (fibers) 112*03ce13f7SAndroid Build Coastguard Worker 113*03ce13f7SAndroid Build Coastguard Worker `runUntilIdle()` begins by completing all fibers that are ready to be resumed (no longer blocked). 114*03ce13f7SAndroid Build Coastguard Worker This is done by taking a fiber from the `work.fibers` queue, placing the current fiber into the `idleFibers` queue (this fiber is considered idle as it is looking for work), and switching the context over to the taken fiber. 115*03ce13f7SAndroid Build Coastguard Worker 116*03ce13f7SAndroid Build Coastguard Worker Executing unblocked fibers is prioritized over starting new tasks. This is because new tasks may result in yet more fibers, and each fiber consumes a certain amount of memory (typically for stack). 117*03ce13f7SAndroid Build Coastguard Worker 118*03ce13f7SAndroid Build Coastguard Worker2. Start executing new tasks 119*03ce13f7SAndroid Build Coastguard Worker 120*03ce13f7SAndroid Build Coastguard Worker Once all resumable fibers have been completed or have become re-blocked, new tasks are taken from the `work.tasks` queue, and are executed. Once a task is completed, control returns back to `runUntilIdle()`, and the main loop starts again from 1. 121*03ce13f7SAndroid Build Coastguard Worker 122*03ce13f7SAndroid Build Coastguard Worker3. Once there's no more fibers or tasks to execute, `runUntilIdle()` returns. 123*03ce13f7SAndroid Build Coastguard Worker 124*03ce13f7SAndroid Build Coastguard Worker 125*03ce13f7SAndroid Build Coastguard Worker 126*03ce13f7SAndroid Build Coastguard Worker### `marl::Scheduler::Worker::waitForWork()` 127*03ce13f7SAndroid Build Coastguard Worker 128*03ce13f7SAndroid Build Coastguard WorkerWhen a worker runs out of tasks to start and fibers to resume, `waitForWork()` is called to block until there's something for the worker to do. 129*03ce13f7SAndroid Build Coastguard Worker 130*03ce13f7SAndroid Build Coastguard WorkerIf the worker is a [Multi-Threaded-Worker](#multi-threaded-workers), `waitForWork()` begins by entering [`spinForWork()`](#marlschedulerworkerspinforwork), otherwise this stage is skipped. 131*03ce13f7SAndroid Build Coastguard Worker 132*03ce13f7SAndroid Build Coastguard Worker`waitForWork()` then waits for any of the following to occur before returning: 133*03ce13f7SAndroid Build Coastguard Worker 134*03ce13f7SAndroid Build Coastguard Worker- A fiber becoming ready to be resumed, by being enqueued on the `work.fibers` queue. 135*03ce13f7SAndroid Build Coastguard Worker- A task becoming enqueued on the `work.tasks` queue. 136*03ce13f7SAndroid Build Coastguard Worker- A fiber timing out in the `work.waiting` queue. 137*03ce13f7SAndroid Build Coastguard Worker- The worker being shutdown. 138*03ce13f7SAndroid Build Coastguard Worker 139*03ce13f7SAndroid Build Coastguard WorkerAny fibers that have timed out in the `work.waiting` queue are automatically moved onto the `work.fibers` queue before returning. 140*03ce13f7SAndroid Build Coastguard Worker 141*03ce13f7SAndroid Build Coastguard Worker 142*03ce13f7SAndroid Build Coastguard Worker 143*03ce13f7SAndroid Build Coastguard Worker### `marl::Scheduler::Worker::spinForWork()` 144*03ce13f7SAndroid Build Coastguard Worker 145*03ce13f7SAndroid Build Coastguard Worker`spinForWork()` has two roles: 146*03ce13f7SAndroid Build Coastguard Worker 147*03ce13f7SAndroid Build Coastguard Worker1. It attempts to steal work from other workers to keep worker work-loads evenly balanced. 148*03ce13f7SAndroid Build Coastguard Worker 149*03ce13f7SAndroid Build Coastguard Worker Task lengths can vary significantly in duration, and over time some workers can end up with a large queue of work, while others are starved. `spinForWork()` is only called when the worker is starved, and will attempt to steal tasks from randomly picked workers. Because fibers must only be executed on the same thread, only tasks, not fibers can be stolen. 150*03ce13f7SAndroid Build Coastguard Worker 151*03ce13f7SAndroid Build Coastguard Worker2. It attempts to avoid yielding the thread to the OS. 152*03ce13f7SAndroid Build Coastguard Worker 153*03ce13f7SAndroid Build Coastguard Worker It is common to have a single task (provider) scheduling many small sub-tasks to the scheduler, which are evenly distributed to the workers (consumers). These consumers typically outnumber the providers, and it is easy to have the provider struggle to provide enough work to keep the consumers fully occupied. 154*03ce13f7SAndroid Build Coastguard Worker 155*03ce13f7SAndroid Build Coastguard Worker In this situation, the workers can enter a loop where they are given a task, complete it, and end up waiting a short duration for more work. Allowing a worker thread to yield to the OS when waiting for another task (e.g. with `std::condition_variable::wait()`) can be costly in terms of performance. Depending on the platform, it may take a millisecond or more before the thread is resumed by the OS. A stall of this length can lead to significant stalls in the entire task dependency graph. 156*03ce13f7SAndroid Build Coastguard Worker 157*03ce13f7SAndroid Build Coastguard Worker`spinForWork()` contains a loop that runs for a short duration. In the body of the loop, the following is performed: 158*03ce13f7SAndroid Build Coastguard Worker 159*03ce13f7SAndroid Build Coastguard Worker- A tight loop of `nops` is used to keep the CPU busy, while periodically checking `work.num` to see if any new work has become available. If new work is found, `spinForWork()` returns immediately. 160*03ce13f7SAndroid Build Coastguard Worker- If no new work was scheduled, an attempt is made to steal a task from another random worker. If the steal was successful, `spinForWork()` returns immediately. 161*03ce13f7SAndroid Build Coastguard Worker- If the steal was unsuccessful, `std::this_thread::yield()` is called to prevent marl from starving the OS. 162*03ce13f7SAndroid Build Coastguard Worker 163*03ce13f7SAndroid Build Coastguard Worker 164*03ce13f7SAndroid Build Coastguard Worker 165*03ce13f7SAndroid Build Coastguard Worker### `marl::Scheduler::Worker::suspend()` 166*03ce13f7SAndroid Build Coastguard Worker 167*03ce13f7SAndroid Build Coastguard WorkerMarl allows tasks to block, while keeping threads busy. 168*03ce13f7SAndroid Build Coastguard Worker 169*03ce13f7SAndroid Build Coastguard WorkerIf a task blocks, then `Scheduler::Worker::suspend()` is called. `suspend()` begins by calling [`Scheduler::Worker::waitForWork()`](#marlschedulerworkerwaitforwork), which blocks until there's a task or fiber that can be executed. Then, one of the following occurs: 170*03ce13f7SAndroid Build Coastguard Worker 171*03ce13f7SAndroid Build Coastguard Worker 1. If there's any unblocked fibers, the fiber is taken from the `work.fibers` queue and is switched to. 172*03ce13f7SAndroid Build Coastguard Worker 2. If there's any idle fibers, one is taken from the `idleFibers` set and is switched to. This idle fiber when resumed, will continue the role of executing tasks. 173*03ce13f7SAndroid Build Coastguard Worker 3. If none of the above occurs, then a new fiber needs to be created to continue executing tasks. This fiber is created to begin execution in [`marl::Scheduler::Worker::run()`](#marlschedulerworkerrun), and is switched to. 174*03ce13f7SAndroid Build Coastguard Worker 175*03ce13f7SAndroid Build Coastguard WorkerIn all cases, the `suspend()` call switches to another fiber. When the suspended fiber is resumed, `suspend()` returns back to the caller. 176*03ce13f7SAndroid Build Coastguard Worker 177*03ce13f7SAndroid Build Coastguard Worker 178*03ce13f7SAndroid Build Coastguard Worker 179*03ce13f7SAndroid Build Coastguard Worker## Worker Types 180*03ce13f7SAndroid Build Coastguard Worker 181*03ce13f7SAndroid Build Coastguard WorkerA worker is created as either a Single-Threaded-Worker or Multi-Threaded-Worker. 182*03ce13f7SAndroid Build Coastguard Worker 183*03ce13f7SAndroid Build Coastguard WorkerThe majority of the logic is identical between the two modes. 184*03ce13f7SAndroid Build Coastguard Worker 185*03ce13f7SAndroid Build Coastguard WorkerThe most significant difference is that the Multi-Threaded-Worker spawns a dedicated worker thread to call `marl::Scheduler::run()`, whereas the Single-Threaded-Worker will only call `marl::Scheduler::run()` on a new fiber, when all other fibers become blocked. 186*03ce13f7SAndroid Build Coastguard Worker 187*03ce13f7SAndroid Build Coastguard Worker### Single-Threaded-Workers 188*03ce13f7SAndroid Build Coastguard Worker 189*03ce13f7SAndroid Build Coastguard WorkerA single-threaded-worker (STW) is created for each thread that is bound with a call to `marl::Scheduler::bind()`. 190*03ce13f7SAndroid Build Coastguard Worker 191*03ce13f7SAndroid Build Coastguard WorkerIf the scheduler has no dedicated worker threads (`marl::Scheduler::config().workerThreads.count == 0`), then scheduled tasks are queued on to the STW for the currently executing thread. 192*03ce13f7SAndroid Build Coastguard Worker 193*03ce13f7SAndroid Build Coastguard WorkerBecause in this mode there are no worker threads, the tasks queued on the STW are not automatically background executed. Instead, tasks are only executed whenever there's a call to [`marl::Scheduler::Worker::suspend()`](#marlschedulerworkersuspend). 194*03ce13f7SAndroid Build Coastguard WorkerThe logic for [`suspend()`](#marlschedulerworkersuspend) is common for STWs and MTWs, spawning new fibers that call [`marl::Scheduler::Worker::run()`](#marlschedulerworkerrun) whenever all other fibers are blocked. 195*03ce13f7SAndroid Build Coastguard Worker 196*03ce13f7SAndroid Build Coastguard Worker```c++ 197*03ce13f7SAndroid Build Coastguard Workervoid SingleThreadedWorkerExample() { 198*03ce13f7SAndroid Build Coastguard Worker marl::Scheduler::Config cfg; 199*03ce13f7SAndroid Build Coastguard Worker cfg.setWorkerThreadCount(0); // STW mode. 200*03ce13f7SAndroid Build Coastguard Worker 201*03ce13f7SAndroid Build Coastguard Worker marl::Scheduler scheduler(cfg); 202*03ce13f7SAndroid Build Coastguard Worker scheduler.bind(); 203*03ce13f7SAndroid Build Coastguard Worker defer(scheduler.unbind()); 204*03ce13f7SAndroid Build Coastguard Worker 205*03ce13f7SAndroid Build Coastguard Worker // Calling marl::schedule() enqueues the task on the STW, but does not 206*03ce13f7SAndroid Build Coastguard Worker // execute it until the thread is blocked. 207*03ce13f7SAndroid Build Coastguard Worker marl::Event done; 208*03ce13f7SAndroid Build Coastguard Worker marl::schedule([=] { 209*03ce13f7SAndroid Build Coastguard Worker done.signal(); 210*03ce13f7SAndroid Build Coastguard Worker }); 211*03ce13f7SAndroid Build Coastguard Worker 212*03ce13f7SAndroid Build Coastguard Worker // This is a blocking call. 213*03ce13f7SAndroid Build Coastguard Worker // marl::Event::wait() (indirectly) calls marl::Scheduler::Worker::suspend(). 214*03ce13f7SAndroid Build Coastguard Worker // marl::Scheduler::Worker::suspend() creates and switches to a fiber which 215*03ce13f7SAndroid Build Coastguard Worker // calls marl::Scheduler::Worker::run() to run all enqueued tasks. Once the 216*03ce13f7SAndroid Build Coastguard Worker // main fiber becomes unblocked, marl::Scheduler::Worker::runUntilIdle() will 217*03ce13f7SAndroid Build Coastguard Worker // switch back to the main fiber to continue execution of the application. 218*03ce13f7SAndroid Build Coastguard Worker done.wait(); 219*03ce13f7SAndroid Build Coastguard Worker} 220*03ce13f7SAndroid Build Coastguard Worker``` 221*03ce13f7SAndroid Build Coastguard Worker 222*03ce13f7SAndroid Build Coastguard Worker### Multi-Threaded-Workers 223*03ce13f7SAndroid Build Coastguard Worker 224*03ce13f7SAndroid Build Coastguard WorkerMulti-Threaded-Workers are created when the `marl::Scheduler` is constructed with a positive number of worker threads (`marl::Scheduler::Config::workerThread::count > 0`). 225*03ce13f7SAndroid Build Coastguard Worker 226*03ce13f7SAndroid Build Coastguard WorkerEach MTW is paired with a new `std::thread` that begins by calling `marl::Scheduler::Worker::run()`. 227*03ce13f7SAndroid Build Coastguard Worker 228*03ce13f7SAndroid Build Coastguard WorkerWhen the worker is told to shut down and all work is complete, `marl::Scheduler::Worker::run()` exits the main processing loop, and switches back to the main thread fiber which ends the `std::thread`. 229