{"id":15032126,"url":"https://github.com/david-haim/concurrencpp","last_synced_at":"2025-05-14T06:13:54.493Z","repository":{"id":21747070,"uuid":"232289062","full_name":"David-Haim/concurrencpp","owner":"David-Haim","description":"Modern concurrency for C++. Tasks, executors, timers and C++20 coroutines to rule them all","archived":false,"fork":false,"pushed_at":"2025-03-11T06:04:24.000Z","size":1144,"stargazers_count":2515,"open_issues_count":13,"forks_count":224,"subscribers_count":54,"default_branch":"master","last_synced_at":"2025-04-11T16:34:19.503Z","etag":null,"topics":["async-await","asynchronous-programming","await","concurrency","concurrent-programming","coroutine","coroutines","cpp","cpp20","executor-service","executors","multithreading","scheduler","task-scheduler","tasks","thread-safety","threading","threadpool","timers"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/David-Haim.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-01-07T09:24:06.000Z","updated_at":"2025-04-11T14:58:56.000Z","dependencies_parsed_at":"2023-10-20T19:46:10.909Z","dependency_job_id":"154627fe-c933-47c4-8795-cc48070750ac","html_url":"https://github.com/David-Haim/concurrencpp","commit_stats":{"total_commits":23,"total_committers":3,"mean_commits":7.666666666666667,"dds":0.4347826086956522,"last_synced_commit":"7a88f5401e15e1b64acae70077e40df1a5a9f6bf"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/David-Haim%2Fconcurrencpp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/David-Haim%2Fconcurrencpp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/David-Haim%2Fconcurrencpp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/David-Haim%2Fconcurrencpp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/David-Haim","download_url":"https://codeload.github.com/David-Haim/concurrencpp/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248441391,"owners_count":21103985,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async-await","asynchronous-programming","await","concurrency","concurrent-programming","coroutine","coroutines","cpp","cpp20","executor-service","executors","multithreading","scheduler","task-scheduler","tasks","thread-safety","threading","threadpool","timers"],"created_at":"2024-09-24T20:17:23.515Z","updated_at":"2025-04-11T16:34:49.761Z","avatar_url":"https://github.com/David-Haim.png","language":"C++","readme":"# concurrencpp, the C++ concurrency library\n\n![Latest Release](https://img.shields.io/github/v/release/David-Haim/concurrencpp.svg) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nconcurrencpp brings the power of concurrent tasks to the C++ world, allowing developers to write highly concurrent applications easily and safely by using tasks, executors and coroutines.\nBy using concurrencpp applications can break down big procedures that need to be processed asynchronously into smaller tasks that run concurrently and work in a co-operative manner to achieve the wanted result.\nconcurrencpp also allows applications to write parallel algorithms easily by using parallel coroutines.\n\nconcurrencpp main advantages are:\n* Writing modern concurrency code using higher level tasks instead of low level primitives like `std::thread` and `std::mutex`.\n* Writing highly concurrent and parallel applications that scale automatically to use all hardware resources, as needed.\n* Achieving non-blocking, synchronous-like code easily by using C++20 coroutines and the `co_await` keyword.\n* Reducing the possibility of race conditions, data races and deadlocks by using high-level objects with built-in synchronization.\n* concurrencpp provides various types of commonly used executors with a complete coroutine integration.\n* Applications can extend the library by implementing their own provided executors.\n* concurrencpp is mature and well tested on various platforms and operating systems.\n\n----\n ### Table of contents\n* [concurrencpp overview](#concurrencpp-overview)\n* [Tasks](#tasks)\n    * [concurrencpp coroutines](#concurrencpp-coroutines )\n* [Executors](#executors)\n    * [`executor` API](#executor-api)\n    * [Executor types](#executor-types)\n    * [Using executors](#using-executors)\n    * [`thread_pool_executor` API](#thread_pool_executor-api)\n    * [`manual_executor` API](#manual_executor-api)\n* [Result objects](#result-objects)\n\t* [`result` type](#result-type)\n    * [`result` API](#result-api)\n\t* [`lazy_result` type](#lazy_result-type)\n    * [`lazy_result` API](#lazy_result-api)\n* [Parallel coroutines](#parallel-coroutines)\n    * [Parallel Fibonacci example](#parallel-fibonacci-example)\n* [Result-promises](#result-promises)\n    * [`result_promise` API](#result_promise-api)\n    * [`result_promise` example](#result_promise-example)\n* [Shared result objects](#shared-result-objects)\n    * [`shared_result` API](#shared_result-api)\n    * [`shared_result` example](#shared_result-example)\n* [Termination in concurrencpp](#termination-in-concurrencpp)\n* [Resume executors](#resume-executors)\n* [Utility functions](#utility-functions)\n    * [`make_ready_result`](#make_ready_result-function)\n    * [`make_exceptional_result`](#make_exceptional_result-function)\n    * [`when_all`](#when_all-function)\n    * [`when_any`](#when_any-function)\n    * [`resume_on`](#resume_on-function)\n* [Timers and Timer queues](#timers-and-timer-queues)\n    * [`timer_queue` API](#timer_queue-api)\n    * [`timer` API](#timer-api)\n    * [Regular timer example](#regular-timer-example)\n    * [Oneshot timers](#oneshot-timers)\n    * [Oneshot timer example](#oneshot-timer-example)\n    * [Delay objects](#delay-objects)\n    * [Delay object example](#delay-object-example)\n* [Generators](#generators)     \n\t* [`generator` API](#generator-api)\n\t* [`generator` example](#generator-example)\n* [Asynchronous locks](#asynchronous-locks)     \n\t* [`async_lock` API](#async_lock-api)\n\t* [`scoped_async_lock` API](#scoped_async_lock-api)\n\t* [`async_lock` example](#async_lock-example)\n* [Asynchronous condition variable](#asynchronous-condition-variables)     \n\t* [`async_condition_variable` API](#async_condition_variable-api)\n\t* [`async_condition_variable` example](#async_condition_variable-example)\n* [The runtime object](#the-runtime-object)\n    * [`runtime` API](#runtime-api)\n    * [Thread creation and termination monitoring](#thread-creation-and-termination-monitoring)\n    * [Creating user-defined executors](#creating-user-defined-executors)\n    * [`task` objects](#task-objects)\n    * [`task` API](#task-api)\n    * [Writing a user-defined executor example](#example-writing-a-user-defined-executor)\n* [Supported platforms and tools](#supported-platforms-and-tools)\n* [Building, installing and testing](#building-installing-and-testing)\n\n----\n\n###  concurrencpp overview\n\nconcurrencpp is built around the concept of concurrent tasks. A task is an asynchronous operation. Tasks offer a higher level of abstraction for concurrent code than traditional thread-centric approaches. Tasks can be chained together, meaning that tasks pass their asynchronous result from one to another, where the result of one task is used as if it were a parameter or an intermediate value of another ongoing task. Tasks allow applications to utilize available hardware resources better and scale much more than using raw threads, since tasks can be suspended, awaiting another task to produce a result, without blocking underlying OS-threads. Tasks bring much more productivity to developers by allowing them to focus more on business-logic and less on low-level concepts like thread management and inter-thread synchronization.\n\nWhile tasks specify *what* actions have to be executed, *executors* are worker-objects that specify *where and how* to execute tasks. Executors spare applications the tedious management of thread pools and task queues. Executors also decouple those concepts away from application code, by providing a unified API for creating and scheduling tasks.\n\nTasks communicate with each other using *result objects*. A result object is an asynchronous pipe that pass the asynchronous result of one task to another ongoing-task. Results can be awaited and resolved in a non-blocking manner.\n\nThese three concepts - the task, the executor and the associated result are the building blocks of concurrencpp. Executors run tasks that communicate with each other by sending results through result-objects. Tasks, executors and result objects work together symbiotically to produce concurrent code which is fast and clean.\n\nconcurrencpp is built around the RAII concept. In order to use tasks and executors, applications create a `runtime` instance in the beginning of the `main` function. The runtime is then used to acquire existing executors and register new user-defined executors. Executors are used to create and schedule tasks to run, and they might return a `result` object that can be used to pass the asynchronous result to another task that acts as its consumer.\nWhen the runtime is destroyed, it iterates over every stored executor and calls its `shutdown` method. Every executor then exits gracefully. Unscheduled tasks are destroyed, and attempts to create new tasks will throw an exception.\n\n#### *\"Hello world\" program using concurrencpp:*\n\n```cpp\n#include \"concurrencpp/concurrencpp.h\"\n#include \u003ciostream\u003e\n\nint main() {\n    concurrencpp::runtime runtime;\n    auto result = runtime.thread_executor()-\u003esubmit([] {\n        std::cout \u003c\u003c \"hello world\" \u003c\u003c std::endl;\n    });\n\n    result.get();\n    return 0;\n}\n```\n\nIn this basic example, we created a runtime object, then we acquired the thread executor from the runtime. We used `submit` to pass a lambda as our given callable. This lambda returns `void`, hence, the executor returns a `result\u003cvoid\u003e` object that passes the asynchronous result back to the caller.  `main` calls  `get` which blocks the main thread until the result becomes ready. If no exception was thrown, `get` returns `void`. If an exception was thrown, `get` re-throws it. Asynchronously, `thread_executor` launches a new thread of execution and runs the given lambda. It implicitly `co_return void` and the task is finished. `main` is then unblocked.\n      \n#### *Concurrent even-number counting:*\n\n```cpp\n#include \"concurrencpp/concurrencpp.h\"\n\n#include \u003ciostream\u003e\n#include \u003cvector\u003e\n#include \u003calgorithm\u003e\n\n#include \u003cctime\u003e\n\nusing namespace concurrencpp;\n\nstd::vector\u003cint\u003e make_random_vector() {\n    std::vector\u003cint\u003e vec(64 * 1'024);\n\n    std::srand(std::time(nullptr));\n    for (auto\u0026 i : vec) {\n        i = ::rand();\n    }\n\n    return vec;\n}\n\nresult\u003csize_t\u003e count_even(std::shared_ptr\u003cthread_pool_executor\u003e tpe, const std::vector\u003cint\u003e\u0026 vector) {\n    const auto vecor_size = vector.size();\n    const auto concurrency_level = tpe-\u003emax_concurrency_level();\n    const auto chunk_size = vecor_size / concurrency_level;\n\n    std::vector\u003cresult\u003csize_t\u003e\u003e chunk_count;\n\n    for (auto i = 0; i \u003c concurrency_level; i++) {\n        const auto chunk_begin = i * chunk_size;\n        const auto chunk_end = chunk_begin + chunk_size;\n        auto result = tpe-\u003esubmit([\u0026vector, chunk_begin, chunk_end]() -\u003e size_t {\n            return std::count_if(vector.begin() + chunk_begin, vector.begin() + chunk_end, [](auto i) {\n                return i % 2 == 0;\n            });\n        });\n\n        chunk_count.emplace_back(std::move(result));\n    }\n\n    size_t total_count = 0;\n\n    for (auto\u0026 result : chunk_count) {\n        total_count += co_await result;\n    }\n\n    co_return total_count;\n}\n\nint main() {\n    concurrencpp::runtime runtime;\n    const auto vector = make_random_vector();\n    auto result = count_even(runtime.thread_pool_executor(), vector);\n    const auto total_count = result.get();\n    std::cout \u003c\u003c \"there are \" \u003c\u003c total_count \u003c\u003c \" even numbers in the vector\" \u003c\u003c std::endl;\n    return 0;\n}\n```\n\nIn this example, we start the program by creating a runtime object. We create a vector filled with random numbers, then we acquire the `thread_pool_executor` from the runtime and call `count_even`.\n`count_even` is a coroutine that spawns more tasks and `co_await`s for them to finish inside.\n`max_concurrency_level` returns the maximum amount of workers that the executor supports, In the threadpool executor case, the number of workers is calculated from the number of cores.\nWe then partition the array to match the number of workers and send every chunk to be processed in its own task.\nAsynchronously, the workers count how many even numbers each chunk contains, and `co_return` the result.\n`count_even` sums every result by pulling the count using `co_await`, the final result is then `co_return`ed.\nThe main thread, which was blocked by calling `get` is unblocked and the total count is returned.\nmain prints the number of even numbers and the program terminates gracefully.    \n\n###  Tasks\nEvery big or complex operation can be broken down to smaller and chainable steps.\nTasks are asynchronous operations implementing those computational steps. Tasks can run anywhere with the help of executors. While tasks can be created from regular callables (such as functors and lambdas), Tasks are mostly used with coroutines, which allow smooth suspension and resumption. In concurrencpp, the task concept is represented by the `concurrencpp::task` class. Although the task concept is central to concurrenpp, applications will rarely have to create and manipulate task objects themselves, as task objects are created and scheduled by the runtime with no external help.  \n\n####  concurrencpp coroutines\n\nconcurrencpp allows applications to produce and consume coroutines as the main way of creating tasks. concurrencpp supports both eager and lazy tasks.\n\nEager tasks start to run the moment they are invoked. This type of execution is recommended when applications need to fire an asynchronous action and consume its result later on (fire and consume later), or completely ignore the asynchronous result (fire and forget).\n \nEager tasks can return  `result` or `null_result`. `result` return type tells the coroutine to pass the returned value or the thrown exception (fire and consume later)  while `null_result` return type tells the coroutine to drop and ignore any of them (fire and forget).\n\nEager coroutines can start to run synchronously, in the caller thread. This kind of coroutines is called \"regular coroutines\".\nConcurrencpp eager coroutines can also start to run in parallel, inside a given executor, this kind of coroutines is called \"parallel coroutines\".\n\nLazy tasks, on the other hand, start to run only when `co_await`ed. This type of tasks is recommended when the result of the task is meant to be consumed immediately after creating the task. Lazy tasks, being deferred, are a bit more optimized for the case of immediate-consumption, as they do not need special thread-synchronization in order  to pass the asynchronous result back to its consumer. The compiler might also optimize away some memory allocations needed to form the underlying coroutine promise. It is not possible to fire a lazy task and execute something else meanwhile  - the firing of a lazy-callee coroutine necessarily means the suspension of the caller-coroutine. The caller coroutine will only be resumed when the lazy-callee coroutine completes. Lazy tasks can only return `lazy_result`.  \n\nLazy tasks can be converted to eager tasks by calling  `lazy_result::run`. This method runs the lazy task inline and returns a `result` object that monitors the newly started task. If developers are unsure which result type to use, they are encouraged to use lazy results, as they can be converted to regular (eager) results if needed.  \n\nWhen a function returns any of `lazy_result`, `result` or `null_result`and contains at least one `co_await` or `co_return` in its body, the function is a concurrencpp coroutine. Every valid concurrencpp coroutine is a valid task. In our count-even example above, `count_even` is such a coroutine. We first spawned `count_even`, then inside it the threadpool executor spawned more child tasks (that are created from regular callables),  that were eventually joined using `co_await`.\n\n### Executors\n\nA concurrencpp executor is an object that is able to schedule and run tasks.\nExecutors simplify the work of managing resources such as threads, thread pools and task queues by decoupling them away from application code.\nExecutors provide a unified way of scheduling and executing tasks, since they all extend `concurrencpp::executor`.\n\n#### `executor` API\n\n```cpp\nclass executor {\n    /*\n        Initializes a new executor and gives it a name.\n    */\n    executor(std::string_view name);\n\n    /*\n        Destroys this executor.\n    */\n    virtual ~executor() noexcept = default;\n\n    /*\n        The name of the executor, used for logging and debugging.\n    */\n    const std::string name;\n\n    /*\n        Schedules a task to run in this executor.\n        Throws concurrencpp::errors::runtime_shutdown exception if shutdown was called before.\n    */\n    virtual void enqueue(concurrencpp::task task) = 0;\n\n    /*\n        Schedules a range of tasks to run in this executor.\n        Throws concurrencpp::errors::runtime_shutdown exception if shutdown was called before.\n    */    \n    virtual void enqueue(std::span\u003cconcurrencpp::task\u003e tasks) = 0;\n\n    /*\n        Returns the maximum count of real OS threads this executor supports.\n        The actual count of threads this executor is running might be smaller than this number.\n        returns numeric_limits\u003cint\u003e::max if the executor does not have a limit for OS threads.\n    */\n    virtual int max_concurrency_level() const noexcept = 0;\n\n    /*\n        Returns true if shutdown was called before, false otherwise.\n    */\n    virtual bool shutdown_requested() const noexcept = 0;\n\n    /*\n        Shuts down the executor:\n        - Tells underlying threads to exit their work loop and joins them.\n        - Destroys unexecuted coroutines.\n        - Makes subsequent calls to enqueue, post, submit, bulk_post and\n            bulk_submit to throw concurrencpp::errors::runtime_shutdown exception.\n        - Makes shutdown_requested return true.\n    */\n    virtual void shutdown() noexcept = 0;\n\n    /*\n        Turns a callable and its arguments into a task object and\n        schedules it to run in this executor using enqueue.\n        Arguments are passed to the task by decaying them first.\n        Throws errors::runtime_shutdown exception if shutdown has been called before.\n    */\n    template\u003cclass callable_type, class ... argument_types\u003e\n    void post(callable_type\u0026\u0026 callable, argument_types\u0026\u0026 ... arguments);\n    \n    /*\n        Like post, but returns a result object that passes the asynchronous result.\n        Throws errors::runtime_shutdown exception if shutdown has been called before.\n    */\n    template\u003cclass callable_type, class ... argument_types\u003e\n    result\u003ctype\u003e submit(callable_type\u0026\u0026 callable, argument_types\u0026\u0026 ... arguments);\n\n    /*\n        Turns an array of callables into an array of tasks and\n        schedules them to run in this executor using enqueue.\n        Throws errors::runtime_shutdown exception if shutdown has been called before.\n    */\n    template\u003cclass callable_type\u003e\n    void bulk_post(std::span\u003ccallable_type\u003e callable_list);\n\n    /*\n        Like bulk_post, but returns an array of result objects that passes the asynchronous results.\n        Throws errors::runtime_shutdown exception if shutdown has been called before.\n    */    \n    template\u003cclass callable_type\u003e\n    std::vector\u003cconcurrencpp::result\u003ctype\u003e\u003e bulk_submit(std::span\u003ccallable_type\u003e callable_list);\n};\n```\n\n#### Executor types\n\nAs mentioned above, concurrencpp provides commonly used executors. These executor types are:\n\n* **thread pool executor** - a general purpose executor that maintains a pool of threads.\nThe thread pool executor is suitable for short cpu-bound tasks that don't block. Applications are encouraged to use this executor as the default executor for non-blocking tasks.\nThe concurrencpp thread pool provides dynamic thread injection and dynamic work balancing.\n\n* **background executor** - a threadpool executor with a larger pool of threads. Suitable for launching short blocking tasks like file io and db queries. Important note: when consuming results this executor returned by calling `submit` and `bulk_submit`, it is important to switch execution using `resume_on` to a cpu-bound executor, in order to prevent cpu-bound tasks to be processed inside background_executor.\n\nexample:\n```cpp\n    auto result = background_executor.submit([] { /* some blocking action */ });\n    auto done_result = co_await result.resolve();\n    co_await resume_on(some_cpu_executor);\n    auto val = co_await done_result;  // runs inside some_cpu_executor\n```\n* **thread executor** - an executor that launches each enqueued task to run on a new thread of execution. Threads are not reused.\nThis executor is good for long running tasks, like objects that run a work loop, or long blocking operations.\n\n* **worker thread executor** - a single thread executor that maintains a single task queue. Suitable when applications want a dedicated thread that executes many related tasks.\n\n* **manual executor** - an executor that does not execute coroutines by itself. Application code can execute previously enqueued tasks by manually invoking its execution methods.\n\n* **derivable executor** - a base class for user defined executors. Although inheriting  directly from `concurrencpp::executor` is possible, `derivable_executor` uses the `CRTP` pattern that provides some optimization opportunities for the compiler.\n \n* **inline executor** - mainly used to override the behavior of other executors. Enqueuing a task is equivalent to invoking it inline.\n\n#### Using executors\n\nThe bare mechanism of an executor is encapsulated in its `enqueue` method.\nThis method enqueues a task for execution and has two overloads:\nOne overload receives a single task object as an argument, and another that receives a span of task objects.\nThe second overload is used to enqueue a batch of tasks. This allows better scheduling heuristics and decreased contention.\n\nApplications don't have to rely on `enqueue` alone, `concurrencpp::executor` provides an API for scheduling user callables by converting them to task objects behind the scenes.\nApplications can request executors to return a result object that passes the asynchronous result of the provided callable. This is done by calling `executor::submit` and `executor::bulk_submit`.\n`submit` gets a callable, and returns a result object. `executor::bulk_submit` gets a `span` of callables and returns a `vector`of result objects in a similar way `submit` works.\nIn many cases, applications are not interested in the asynchronous value or exception. In this case, applications can use `executor:::post` and `executor::bulk_post` to schedule a callable or a `span` of callables to be executed, but also tells the task to drop any returned value or thrown exception. Not passing the asynchronous result is faster than passing , but then we have no way of knowing the status or the result of the ongoing task.\n\n`post`, `bulk_post`, `submit` and `bulk_submit` use `enqueue` behind the scenes for the underlying scheduling mechanism.\n\n\n#### `thread_pool_executor` API\n\nAside from `post`, `submit`, `bulk_post` and `bulk_submit`, the `thread_pool_executor`  provides these additional methods.  \n\n```cpp\nclass thread_pool_executor {\n\n    /*\n        Returns the number of milliseconds each thread-pool worker\n        remains idle (lacks any task to execute) before exiting.\n        This constant can be set by passing a runtime_options object\n        to the constructor of the runtime class.\n    */\n    std::chrono::milliseconds max_worker_idle_time() const noexcept;\n\n};\n```\n#### `manual_executor` API\n\nAside from `post`, `submit`, `bulk_post` and `bulk_submit`, the `manual_executor`  provides these additional methods.\n\n```cpp\nclass manual_executor {\n\n    /*\n        Destructor. Equivalent to clear.\n    */\n    ~manual_executor() noexcept;\n\n    /*\n        Returns the number of enqueued tasks at the moment of invocation.\n        This number can change quickly by the time the application handles it, it should be used as a hint.\n        This method is thread safe.\n        Might throw std::system_error if one of the underlying synchronization primitives throws.\n    */\n    size_t size() const noexcept;\n        \n    /*\n        Queries whether the executor is empty from tasks at the moment of invocation.\n        This value can change quickly by the time the application handles it, it should be used as a hint.\n        This method is thread safe.\n        Might throw std::system_error if one of the underlying synchronization primitives throws.\n    */\n    bool empty() const noexcept;\n\n    /*\n        Clears the executor from any enqueued but yet to-be-executed tasks,\n        and returns the number of cleared tasks.\n        Tasks enqueued to this executor by (post_)submit method are resumed\n        and errors::broken_task exception is thrown inside them.\n        Ongoing tasks that are being executed by loop_once(_XXX) or loop(_XXX) are uneffected.\n        This method is thread safe.\n        Might throw std::system_error if one of the underlying synchronization primitives throws.\n        Throws errors::shutdown_exception if shutdown was called before.\n    */\n    size_t clear();\n\n    /*\n        Tries to execute a single task. If at the moment of invocation the executor\n        is empty, the method does nothing.\n        Returns true if a task was executed, false otherwise.\n        This method is thread safe.\n        Might throw std::system_error if one of the underlying synchronization primitives throws. \n        Throws errors::shutdown_exception if shutdown was called before.\n    */\n    bool loop_once();\n\n    /*\n        Tries to execute a single task.\n        This method returns when either a task was executed or max_waiting_time\n        (in milliseconds) has reached.\n        If max_waiting_time is 0, the method is equivalent to loop_once.\n        If shutdown is called from another thread, this method returns\n        and throws errors::shutdown_exception.\n        This method is thread safe.\n        Might throw std::system_error if one of the underlying synchronization primitives throws.\n        Throws errors::shutdown_exception if shutdown was called before.\n    */\n    bool loop_once_for(std::chrono::milliseconds max_waiting_time);\n\n    /*\n        Tries to execute a single task.\n        This method returns when either a task was executed or timeout_time has reached.\n        If timeout_time has already expired, this method is equivalent to loop_once.\n        If shutdown is called from another thread, this method\n        returns and throws errors::shutdown_exception.\n        This method is thread safe.\n        Might throw std::system_error if one of the underlying synchronization primitives throws.\n        Throws errors::shutdown_exception if shutdown was called before.\n    */\n    template\u003cclass clock_type, class duration_type\u003e\n    bool loop_once_until(std::chrono::time_point\u003cclock_type, duration_type\u003e timeout_time);\n   \n    /*\n        Tries to execute max_count enqueued tasks and returns the number of tasks that were executed.\n        This method does not wait: it returns when the executor\n        becomes empty from tasks or max_count tasks have been executed.\n        This method is thread safe.\n        Might throw std::system_error if one of the underlying synchronization primitives throws.\n        Throws errors::shutdown_exception if shutdown was called before.\n    */\n    size_t loop(size_t max_count);\n\n    /*\n        Tries to execute max_count tasks.\n        This method returns when either max_count tasks were executed or a\n        total amount of max_waiting_time has passed.\n        If max_waiting_time is 0, the method is equivalent to loop.\n        Returns the actual amount of tasks that were executed.\n        If shutdown is called from another thread, this method returns\n        and throws errors::shutdown_exception.\n        This method is thread safe.\n        Might throw std::system_error if one of the underlying synchronization primitives throws.\n        Throws errors::shutdown_exception if shutdown was called before.\n    */\n    size_t loop_for(size_t max_count, std::chrono::milliseconds max_waiting_time);\n\n    /*    \n        Tries to execute max_count tasks.\n        This method returns when either max_count tasks were executed or timeout_time has reached.\n        If timeout_time has already expired, the method is equivalent to loop.\n        Returns the actual amount of tasks that were executed.\n        If shutdown is called from another thread, this method returns\n        and throws errors::shutdown_exception.\n        This method is thread safe.\n        Might throw std::system_error if one of the underlying synchronization primitives throws.\n        Throws errors::shutdown_exception if shutdown was called before.\n    */\n    template\u003cclass clock_type, class duration_type\u003e\n    size_t loop_until(size_t max_count, std::chrono::time_point\u003cclock_type, duration_type\u003e timeout_time);\n    \n    /*\n        Waits for at least one task to be available for execution.\n        This method should be used as a hint,\n        as other threads (calling loop, for example) might empty the executor,\n        before this thread has a chance to do something with the newly enqueued tasks.\n        If shutdown is called from another thread, this method returns\n        and throws errors::shutdown_exception.\n        This method is thread safe.\n        Might throw std::system_error if one of the underlying synchronization primitives throws.\n        Throws errors::shutdown_exception if shutdown was called before.\n    */\n    void wait_for_task();\n\n    /*\n        This method returns when one or more tasks are available for\n        execution or max_waiting_time has passed.    \n        Returns true if at at least one task is available for execution, false otherwise.\n        This method should be used as a hint, as other threads (calling loop, for example)\n        might empty the executor, before this thread has a chance to do something\n        with the newly enqueued tasks.\n        If shutdown is called from another thread, this method\n        returns and throws errors::shutdown_exception.\n        This method is thread safe.\n        Might throw std::system_error if one of the underlying synchronization primitives throws.\n        Throws errors::shutdown_exception if shutdown was called before.\n    */\n    bool wait_for_task_for(std::chrono::milliseconds max_waiting_time);\n\n    /*\n        This method returns when one or more tasks are available for execution or timeout_time has reached.    \n        Returns true if at at least one task is available for execution, false otherwise.\n        This method should be used as a hint,\n        as other threads (calling loop, for example) might empty the executor,\n        before this thread has a chance to do something with the newly enqueued tasks.\n        If shutdown is called from another thread, this method\n        returns and throws errors::shutdown_exception.\n        This method is thread safe.\n        Might throw std::system_error if one of the underlying synchronization primitives throws.\n        Throws errors::shutdown_exception if shutdown was called before.\n    */\n    template\u003cclass clock_type, class duration_type\u003e\n    bool wait_for_task_until(std::chrono::time_point\u003cclock_type, duration_type\u003e timeout_time);\n    \n    /*\n        This method returns when max_count or more tasks are available for execution.    \n        This method should be used as a hint, as other threads\n        (calling loop, for example) might empty the executor,\n        before this thread has a chance to do something with the newly enqueued tasks.\n        If shutdown is called from another thread, this method returns\n        and throws errors::shutdown_exception.\n        This method is thread safe. \n        Might throw std::system_error if one of the underlying synchronization primitives throws.\n        Throws errors::shutdown_exception if shutdown was called before.\n    */\n    void wait_for_tasks(size_t max_count);\n\n    /*\n        This method returns when max_count or more tasks are available for execution\n        or max_waiting_time (in milliseconds) has passed.    \n        Returns the number of tasks available for execution when the method returns.\n        This method should be used as a hint, as other\n        threads (calling loop, for example) might empty the executor,\n        before this thread has a chance to do something with the newly enqueued tasks.\n        If shutdown is called from another thread, this method returns\n        and throws errors::shutdown_exception.\n        This method is thread safe.  \n        Might throw std::system_error if one of the underlying synchronization primitives throws.\n        Throws errors::shutdown_exception if shutdown was called before.    \n    */\n    size_t wait_for_tasks_for(size_t count, std::chrono::milliseconds max_waiting_time);\n\n    /*\n        This method returns when max_count or more tasks are available for execution\n        or timeout_time is reached.    \n        Returns the number of tasks available for execution when the method returns.\n        This method should be used as a hint, as other threads\n        (calling loop, for example) might empty the executor,\n        before this thread has a chance to do something with the newly enqueued tasks.\n        If shutdown is called from another thread, this method returns\n        and throws errors::shutdown_exception.\n        This method is thread safe.  \n        Might throw std::system_error if one of the underlying synchronization primitives throws.\n        Throws errors::shutdown_exception if shutdown was called before.    \n    */\n    template\u003cclass clock_type, class duration_type\u003e\n    size_t wait_for_tasks_until(size_t count, std::chrono::time_point\u003cclock_type, duration_type\u003e timeout_time);\n        \n};\n```\n### Result objects\n\nAsynchronous values and exceptions can be consumed using concurrencpp result objects. The `result` type represents the asynchronous result of an eager task while `lazy_result` represents the deferred result of a lazy task. \n\nWhen a task (eager or lazy) completes, it either returns a valid value or throws an exception. In either case, this asynchronous result is passed to the consumer of the result object.\n\n`result` objects form asymmetric coroutines - the execution of a caller-coroutine is not effected by the execution of a callee-coroutine, both coroutines can run independently. Only when consuming the result of the callee-coroutine, the caller-coroutine might be suspended awaiting the callee to complete. Up until that point both coroutines run independently. The callee-coroutine runs whether its result is consumed or not. \n\n`lazy_result` objects form symmetric coroutines - execution of a callee-coroutine happens only after the suspension of the caller-coroutine. When awaiting a lazy result, the current coroutine is suspended and the lazy task associated with the lazy result starts to run. After the callee-coroutine completes and yields a result, the caller-coroutine is resumed. If a lazy result is not consumed, its associated lazy task never starts to run. \n\nAll result objects are a move-only type, and as such, they cannot be used after their content was moved to another result object. In this case, the result object is considered to be empty and attempts to call any method other than `operator bool` and `operator = ` will throw an exception.\n\nAfter the asynchronous result has been pulled out of the result object (for example, by calling `get` or `operator co_await`), the result object becomes empty. Emptiness can be tested with `operator bool`.\n\nAwaiting a result means to suspend the current coroutine until the result object is ready. If a valid value was returned from the associated task, it is returned from the result object. If the associated task throws an exception, it is re-thrown.\nAt the moment of awaiting, if the result is already ready, the current coroutine resumes immediately. Otherwise, it is resumed by the thread that sets the asynchronous result or exception.\n\nResolving a result is similar to awaiting it. The difference is that the `co_await` expression will return the result object itself,\nin a non empty form, in a ready state. The asynchronous result can then be pulled by using `get` or `co_await`.\n\nEvery result object has a status indicating the state of the asynchronous result.\nThe result status varies from `result_status::idle` (the asynchronous result or exception haven't been produced yet) to `result_status::value` (the associated task terminated gracefully by returning a valid value) to `result_status::exception` (the task terminated by throwing an exception).  The status can be queried by calling  `(lazy_)result::status`. \n\n#### `result` type\n\nThe `result` type represents the result of an ongoing, asynchronous task, similar to `std::future`. \n\nAside from awaiting and resolving result-objects, they can also be waited for by calling any of `result::wait`, `result::wait_for`, `result::wait_until` or `result::get`. Waiting for a result to finish is a blocking operation (in the case the asynchronous result is not ready), and will suspend the entire thread of execution waiting for the asynchronous result to become available. Waiting operations are generally discouraged and only allowed in root-level tasks or in contexts which allow it, like blocking the main thread waiting for the rest of the application to finish gracefully, or using `concurrencpp::blocking_executor` or `concurrencpp::thread_executor`.\n\nAwaiting result objects by using `co_await` (and by doing so, turning the current function/task into a coroutine as well) is the preferred way of consuming result objects, as it does not block underlying threads.\n\n#### `result` API\n    \n```cpp\nclass result{\n    /*\n        Creates an empty result that isn't associated with any task.\n    */\n    result() noexcept = default;\n\n    /*\n        Destroys the result. Associated tasks are not cancelled.\n        The destructor does not block waiting for the asynchronous result to become ready.\n    */    \n    ~result() noexcept = default;\n\n    /*\n        Moves the content of rhs to *this. After this call, rhs is empty.\n    */\n    result(result\u0026\u0026 rhs) noexcept = default;\n\n    /*\n        Moves the content of rhs to *this. After this call, rhs is empty. Returns *this.        \n    */\n    result\u0026 operator = (result\u0026\u0026 rhs) noexcept = default;\n\n    /*\n        Returns true if this is a non-empty result.\n        Applications must not use this object if this-\u003eoperator bool() is false.\n    */\n    explicit operator bool() const noexcept;\n\n    /*\n        Queries the status of *this.\n        The returned value is any of result_status::idle, result_status::value or result_status::exception.\n        Throws errors::empty_result if *this is empty.        \n    */\n    result_status status() const;\n\n    /*\n        Blocks the current thread of execution until this result is ready,\n        when status() != result_status::idle.\n        Throws errors::empty_result if *this is empty.\n        Might throw std::bad_alloc if fails to allocate memory.\n        Might throw std::system_error if one of the underlying synchronization primitives throws.                    \n    */\n    void wait();\n\n    /*\n        Blocks until this result is ready or duration has passed. Returns the status\n        of this result after unblocking.\n        Throws errors::empty_result if *this is empty.  \n        Might throw std::bad_alloc if fails to allocate memory.\n        Might throw std::system_error if one of the underlying synchronization primitives throws.\n    */\n    template\u003cclass duration_unit, class ratio\u003e\n    result_status wait_for(std::chrono::duration\u003cduration_unit, ratio\u003e duration);\n\n    /*\n        Blocks until this result is ready or timeout_time has reached. Returns the status\n        of this result after unblocking.\n        Throws errors::empty_result if *this is empty.         \n        Might throw std::bad_alloc if fails to allocate memory.\n        Might throw std::system_error if one of the underlying synchronization primitives throws.\n    */\n    template\u003c class clock, class duration \u003e\n    result_status wait_until(std::chrono::time_point\u003cclock, duration\u003e timeout_time);\n\n    /*\n        Blocks the current thread of execution until this result is ready,\n        when status() != result_status::idle.\n        If the result is a valid value, it is returned, otherwise, get rethrows the asynchronous exception.        \n        Throws errors::empty_result if *this is empty.         \n        Might throw std::bad_alloc if fails to allocate memory.\n        Might throw std::system_error if one of the underlying synchronization primitives throws.           \n    */\n    type get();\n\n    /*\n        Returns an awaitable used to await this result.\n        If the result is already ready - the current coroutine resumes\n        immediately in the calling thread of execution.\n        If the result is not ready yet, the current coroutine is suspended\n        and resumed when the asynchronous result is ready,\n        by the thread which had set the asynchronous value or exception.\n        In either way, after resuming, if the result is a valid value, it is returned.\n        Otherwise, operator co_await rethrows the asynchronous exception.\n        Throws errors::empty_result if *this is empty.                            \n    */\n    auto operator co_await();\n\n    /*\n        Returns an awaitable used to resolve this result.\n        After co_await expression finishes, *this is returned in a non-empty form, in a ready state.\n        Throws errors::empty_result if *this is empty.\n    */    \n    auto resolve();\n};\n```\n#### `lazy_result` type\n\nA lazy result object represents the result of a deferred lazy task. \n\n`lazy_result` has the responsibility of both starting the associated lazy task and passing its deferred result back to its consumer. \nWhen awaited or resolved, the lazy result suspends the current coroutine and starts the associated lazy task. when the associated task completes, its asynchronous value is passed to the caller task, which is then resumed. \n\nSometimes, an API might return a lazy result, but applications need its associated task to run eagerly (without suspending the caller task). In this case, lazy tasks can be converted to eager tasks by calling  `run` on its associated lazy result. In this case, the associated task will start to run inline, without suspending the caller task. The original lazy result is emptied and a valid `result` object that monitors the newly started task will be returned instead.\n\n#### `lazy_result` API\n\n```cpp\nclass lazy_result {\n    /*\n        Creates an empty lazy result that isn't associated with any task.\n    */\n    lazy_result() noexcept = default;\n\n    /*\n        Moves the content of rhs to *this. After this call, rhs is empty.\n    */\n    lazy_result(lazy_result\u0026\u0026 rhs) noexcept;\n\n    /*\n\t    Destroys the result. If not empty, the destructor destroys the associated task without resuming it.\n    */\n    ~lazy_result() noexcept;\n\n    /*\n        Moves the content of rhs to *this. After this call, rhs is empty. Returns *this.\n        If *this is not empty, then operator= destroys the associated task without resuming it.\n    */\n    lazy_result\u0026 operator=(lazy_result\u0026\u0026 rhs) noexcept;\n\n    /*\n        Returns true if this is a non-empty result.\n        Applications must not use this object if this-\u003eoperator bool() is false.\n    */\n    explicit operator bool() const noexcept;\n\n    /*\n        Queries the status of *this.\n        The returned value is any of result_status::idle, result_status::value or result_status::exception.\n        Throws errors::empty_result if *this is empty.  \n    */\n    result_status status() const;\n\n    /*\n        Returns an awaitable used to start the associated task and await this result.\n        If the result is already ready - the current coroutine resumes immediately\n        in the calling thread of execution.\n        If the result is not ready yet, the current coroutine is suspended and\n        resumed when the asynchronous result is ready,\n        by the thread which had set the asynchronous value or exception.\n        In either way, after resuming, if the result is a valid value, it is returned.\n        Otherwise, operator co_await rethrows the asynchronous exception.\n        Throws errors::empty_result if *this is empty.   \n    */\n    auto operator co_await();\n\n    /*\n        Returns an awaitable used to start the associated task and resolve this result.\n        If the result is already ready - the current coroutine resumes immediately\n        in the calling thread of execution.\n        If the result is not ready yet, the current coroutine is suspended and resumed\n        when the asynchronous result is ready, by the thread which\n        had set the asynchronous value or exception.\n        After co_await expression finishes, *this is returned in a non-empty form, in a ready state.\t\n        Throws errors::empty_result if *this is empty.\n    */\n    auto resolve();\n\n    /*\n        Runs the associated task inline and returns a result object that monitors the newly started task.\n        After this call, *this is empty. \n        Throws errors::empty_result if *this is empty.\n        Might throw std::bad_alloc if fails to allocate memory.\n    */\n    result\u003ctype\u003e run();\n};\n```\n\n###  Parallel coroutines\n\nRegular eager coroutines start to run synchronously in the calling thread of execution. Execution might shift to another thread of execution if a coroutine undergoes a rescheduling, for example by awaiting an unready result object inside it.\nconcurrencpp also provides parallel coroutines, which start to run inside a given executor, not in the invoking thread of execution. This style of scheduling coroutines is especially helpful when writing parallel algorithms, recursive algorithms and concurrent algorithms that use the fork-join model.\n\nEvery parallel coroutine must meet the following preconditions:\n\n1. Returns any of `result` / `null_result` .\n2. Gets `executor_tag` as its first argument .\n3. Gets any of `type*` / `type\u0026` / `std::shared_ptr\u003ctype\u003e`, where `type` is a concrete class of `executor` as its second argument.\n4. Contains any of `co_await` or `co_return` in its body.\n5. Is not a member function or a lambda function\n\nIf all the above applies, the function is a parallel coroutine:\nconcurrencpp will start the coroutine suspended and immediately reschedule it to run in the provided executor.\n`concurrencpp::executor_tag` is a dummy placeholder to tell the concurrencpp runtime that this function is not a regular function, it needs to start running inside the given executor.\nIf the executor passed to the parallel coroutine is null, the coroutine will not start to run and an `std::invalid_argument` exception will be thrown synchronously. \nIf all preconditions are met, Applications can consume the result of the parallel coroutine by using the returned result object. \n\n#### Parallel Fibonacci example:\n\nIn this example, we calculate the 30-th member of the Fibonacci sequence in a parallel manner.\nWe start launching each Fibonacci step in its own parallel coroutine. The first argument is a dummy `executor_tag` and the second argument is the threadpool executor.\nEvery recursive step invokes a new parallel coroutine that runs in parallel. Each result is `co_return`ed to its parent task and acquired by using `co_await`.   \nWhen we deem the input to be small enough to be calculated synchronously (when `curr \u003c= 10`), we stop executing each recursive step in its own task and just solve the algorithm synchronously.\n\n```cpp\n#include \"concurrencpp/concurrencpp.h\"\n#include \u003ciostream\u003e\n\nusing namespace concurrencpp;\n\nint fibonacci_sync(int i) {\n    if (i == 0) {\n        return 0;\n    }\n\n    if (i == 1) {\n        return 1;\n    }\n\n    return fibonacci_sync(i - 1) + fibonacci_sync(i - 2);\n}\n\nresult\u003cint\u003e fibonacci(executor_tag, std::shared_ptr\u003cthread_pool_executor\u003e tpe, const int curr) {\n    if (curr \u003c= 10) {\n        co_return fibonacci_sync(curr);\n    }\n\n    auto fib_1 = fibonacci({}, tpe, curr - 1);\n    auto fib_2 = fibonacci({}, tpe, curr - 2);\n\n    co_return co_await fib_1 + co_await fib_2;\n}\n\nint main() {\n    concurrencpp::runtime runtime;\n    auto fibb_30 = fibonacci({}, runtime.thread_pool_executor(), 30).get();\n    std::cout \u003c\u003c \"fibonacci(30) = \" \u003c\u003c fibb_30 \u003c\u003c std::endl;\n    return 0;\n}\n```\n\nTo compare, this is how the same code is written without using parallel coroutines, and relying on `executor::submit` alone.\nSince `fibonacci` returns a `result\u003cint\u003e`, submitting it recursively via `executor::submit` will result a `result\u003cresult\u003cint\u003e\u003e`.\n\n```cpp\n#include \"concurrencpp/concurrencpp.h\"\n#include \u003ciostream\u003e\n\nusing namespace concurrencpp;\n\nint fibonacci_sync(int i) {\n    if (i == 0) {\n        return 0;\n    }\n\n    if (i == 1) {\n        return 1;\n    }\n\n    return fibonacci_sync(i - 1) + fibonacci_sync(i - 2);\n}\n\nresult\u003cint\u003e fibonacci(std::shared_ptr\u003cthread_pool_executor\u003e tpe, const int curr) {\n    if (curr \u003c= 10) {\n        co_return fibonacci_sync(curr);\n    }\n\n    auto fib_1 = tpe-\u003esubmit(fibonacci, tpe, curr - 1);\n    auto fib_2 = tpe-\u003esubmit(fibonacci, tpe, curr - 2);\n\n    co_return co_await co_await fib_1 +\n        co_await co_await fib_2;\n}\n\nint main() {\n    concurrencpp::runtime runtime;\n    auto fibb_30 = fibonacci(runtime.thread_pool_executor(), 30).get();\n    std::cout \u003c\u003c \"fibonacci(30) = \" \u003c\u003c fibb_30 \u003c\u003c std::endl;\n    return 0;\n}\n```\n\n### Result-promises\n\nResult objects are the main way to pass data between tasks in concurrencpp and we've seen how executors and coroutines produce such objects.\nSometimes we want to use the capabilities of result objects with non-tasks, for example when using a third-party library. In this case, we can complete a result object by using a `result_promise`.\n`result_promise` resembles a `std::promise` object - applications can manually set the asynchronous result or exception and make the associated `result` object become ready.\n\nJust like result objects, result-promises are a move only type that becomes empty after move. Similarly, after setting a result or an exception, the result promise becomes empty as well.\nIf a result-promise gets out of scope and no result/exception has been set, the result-promise destructor sets a `concurrencpp::errors::broken_task` exception using the `set_exception` method.\nSuspended and blocked tasks waiting for the associated result object are resumed/unblocked.\n\nResult promises can convert callback style of code into `async/await` style of code: whenever a component requires a callback to pass the asynchronous result, we can pass a callback that calls `set_result` or `set_exception` (depending on the asynchronous result itself) on the passed result promise, and return the associated result.\n\n#### `result_promise` API\n\n```cpp\ntemplate \u003cclass type\u003e\nclass result_promise {    \n    /*\n        Constructs a valid result_promise.\n        Might throw std::bad_alloc if fails to allocate memory.\n    */\n    result_promise();\n\n    /*\n        Moves the content of rhs to *this. After this call, rhs is empty.\n    */        \n    result_promise(result_promise\u0026\u0026 rhs) noexcept;\n\n    /*\n        Destroys *this, possibly setting an errors::broken_task exception\n        by calling set_exception if *this is not empty at the time of destruction.\n    */        \n    ~result_promise() noexcept;\n\n    /*\n        Moves the content of rhs to *this. After this call, rhs is empty.\n    */        \n    result_promise\u0026 operator = (result_promise\u0026\u0026 rhs) noexcept;\n\n    /*\n        Returns true if this is a non-empty result-promise.\n        Applications must not use this object if this-\u003eoperator bool() is false.\n    */\n    explicit operator bool() const noexcept;\n\n    /*\n        Sets a value by constructing \u003c\u003ctype\u003e\u003e from arguments... in-place.\n        Makes the associated result object become ready - tasks waiting for it\n        to become ready are unblocked.\n        Suspended tasks are resumed inline.\n        After this call, *this becomes empty.\n        Throws errors::empty_result_promise exception If *this is empty.\n        Might throw any exception that the constructor\n        of type(std::forward\u003cargument_types\u003e(arguments)...) throws.\n    */\n    template\u003cclass ... argument_types\u003e\n    void set_result(argument_types\u0026\u0026 ... arguments);\n    \n    /*\n        Sets an exception.\n        Makes the associated result object become ready - tasks waiting for it\n        to become ready are unblocked.\n        Suspended tasks are resumed inline.\n        After this call, *this becomes empty.\n        Throws errors::empty_result_promise exception If *this is empty.\n        Throws std::invalid_argument exception if exception_ptr is null.\n    */\n    void set_exception(std::exception_ptr exception_ptr);\n\n    /*\n        A convenience method that invokes a callable with arguments... and calls set_result\n        with the result of the invocation.\n        If an exception is thrown, the thrown exception is caught and set instead by calling set_exception.\n        After this call, *this becomes empty.\n        Throws errors::empty_result_promise exception If *this is empty.\n        Might throw any exception that callable(std::forward\u003cargument_types\u003e(arguments)...)\n        or the contructor of type(type\u0026\u0026) throw. \n    */\n    template\u003cclass callable_type, class ... argument_types\u003e\n    void set_from_function(callable_type\u0026\u0026 callable, argument_types\u0026\u0026 ... arguments);\n    \n    /*\n        Gets the associated result object.\n        Throws errors::empty_result_promise exception If *this is empty.\n        Throws errors::result_already_retrieved exception if this method had been called before.\n    */\n    result\u003ctype\u003e get_result();\n};\n```\n\n#### `result_promise` example:\n\nIn this example, `result_promise` is used to push data from one thread, and it can be pulled from its associated `result` object from another thread. \n\n```cpp\n#include \"concurrencpp/concurrencpp.h\"\n\n#include \u003ciostream\u003e\n\nint main() {\n    concurrencpp::result_promise\u003cstd::string\u003e promise;\n    auto result = promise.get_result();\n\n    std::thread my_3_party_executor([promise = std::move(promise)] () mutable {\n        std::this_thread::sleep_for(std::chrono::seconds(1)); //Imitate real work\n        promise.set_result(\"hello world\");\n    });\n\n    auto asynchronous_string = result.get();\n    std::cout \u003c\u003c \"result promise returned string: \" \u003c\u003c asynchronous_string \u003c\u003c std::endl;\n\n    my_3_party_executor.join();\n}\n```\n\nIn this example, We use `std::thread` as a third-party executor. This represents a scenario when a non-concurrencpp executor is used as part of the application life-cycle. We extract the result object before we pass the promise and block the main thread until the result becomes ready. In `my_3_party_executor`, we set a result as if we `co_return`ed it.\n\n### Shared result objects\n\nShared results are a special kind of result objects that allow multiple consumers to access the asynchronous result, similar to `std::shared_future`.  Different consumers from different threads can call functions like `await`, `get` and `resolve` in a thread safe manner.\n\nShared results are built from regular result objects and unlike regular result objects, they are both copyable and movable. As such, `shared_result` behaves like `std::shared_ptr` type. If a shared result instance is moved to another instance, the instance becomes empty, and trying to access it will throw an exception.\n\nIn order to support multiple consumers, shared results return a *reference* to the asynchronous value instead of moving it (like a regular results). For example, a `shared_result\u003cint\u003e` returns an `int\u0026` when `get`,`await` etc. are called. If the underlying type of the `shared_result` is `void` or a reference type (like `int\u0026`), they are returned as usual. If the asynchronous result is a thrown-exception, it is re-thrown.\n\nDo note that while acquiring the asynchronous result using `shared_result` from multiple threads is thread-safe, the actual value might not be thread safe. For example, multiple threads can acquire an asynchronous integer by receiving its reference (`int\u0026`). It *does not* make the integer itself thread safe. It is alright to mutate the asynchronous value if the asynchronous value is already thread safe. Alternatively, applications are encouraged to use `const` types to begin with (like `const int`), and acquire constant-references (like `const int\u0026`) that prevent mutation.\n\n#### `shared_result` API\n```cpp\nclass share_result {\n    /*\n        Creates an empty shared-result that isn't associated with any task.\n    */\n    shared_result() noexcept = default;\n\n    /*\n        Destroys the shared-result. Associated tasks are not cancelled.\n        The destructor does not block waiting for the asynchronous result to become ready.\n    */    \n    ~shared_result() noexcept = default;\n\n    /*\n        Converts a regular result object to a shared-result object.\n        After this call, rhs is empty.\n        Might throw std::bad_alloc if fails to allocate memory.\n    */\n    shared_result(result\u003ctype\u003e rhs);\n\n    /*\n        Copy constructor. Creates a copy of the shared result object that monitors the same task.\n    */\n    shared_result(const shared_result\u0026) noexcept = default;\n        \n    /*\n        Move constructor. Moves rhs to *this. After this call, rhs is empty.\n    */\n    shared_result(shared_result\u0026\u0026 rhs) noexcept = default;\n        \n    /*\n        Copy assignment operator. Copies rhs to *this and monitors the same task that rhs monitors.  \n    */        \n    shared_result\u0026 operator=(const shared_result\u0026 rhs) noexcept;\n\n    /*\n        Move assignment operator. Moves rhs to *this. After this call, rhs is empty.\n    */\n    shared_result\u0026 operator=(shared_result\u0026\u0026 rhs) noexcept;\n\n    /*\n        Returns true if this is a non-empty shared-result.\n        Applications must not use this object if this-\u003eoperator bool() is false.\n    */\n    explicit operator bool() const noexcept;\n\n    /*\n        Queries the status of *this.\n        The return value is any of result_status::idle, result_status::value or result_status::exception.\n        Throws errors::empty_result if *this is empty.        \n    */\n    result_status status() const;\n\n    /*\n        Blocks the current thread of execution until this shared-result is ready,\n        when status() != result_status::idle.\n        Throws errors::empty_result if *this is empty.  \n        Might throw std::system_error if one of the underlying synchronization primitives throws.                   \n    */\n    void wait();\n\n    /*\n        Blocks until this shared-result is ready or duration has passed.\n        Returns the status of this shared-result after unblocking.\n        Throws errors::empty_result if *this is empty.                    \n        Might throw std::system_error if one of the underlying synchronization primitives throws.\n    */\n    template\u003cclass duration_type, class ratio_type\u003e\n    result_status wait_for(std::chrono::duration\u003cduration_type, ratio_type\u003e duration);\n\n    /*\n        Blocks until this shared-result is ready or timeout_time has reached.\n        Returns the status of this result after unblocking.\n        Throws errors::empty_result if *this is empty.  \n        Might throw std::system_error if one of the underlying synchronization primitives throws.\n    */\n    template\u003cclass clock_type, class duration_type\u003e\n    result_status wait_until(std::chrono::time_point\u003cclock_type, duration_type\u003e timeout_time);\n\n    /*\n        Blocks the current thread of execution until this shared-result is ready,\n        when status() != result_status::idle.\n        If the result is a valid value, a reference to it is returned,\n        otherwise, get rethrows the asynchronous exception.        \n        Throws errors::empty_result if *this is empty.\n        Might throw std::system_error if one of the underlying synchronization primitives throws.\n    */\n    std::add_lvalue_reference_t\u003ctype\u003e get();\n\n    /*\n        Returns an awaitable used to await this shared-result.\n        If the shared-result is already ready - the current coroutine resumes\n        immediately in the calling thread of execution.\n        If the shared-result is not ready yet, the current coroutine is\n        suspended and resumed when the asynchronous result is ready,\n        by the thread which had set the asynchronous value or exception.\n        In either way, after resuming, if the result is a valid value, a reference to it is returned.\n        Otherwise, operator co_await rethrows the asynchronous exception.\n        Throws errors::empty_result if *this is empty.                            \n    */\n    auto operator co_await();\n  \n    /*\n        Returns an awaitable used to resolve this shared-result.\n        After co_await expression finishes, *this is returned in a non-empty form, in a ready state.\n        Throws errors::empty_result if *this is empty.\n    */    \n    auto resolve();\n};\n```\n\n#### `shared_result` example:\n\nIn this example, a `result` object is converted to a `shared_result` object and a reference to an asynchronous `int` result is acquired by many tasks spawned with `thread_executor`.\n\n```cpp\n#include \"concurrencpp/concurrencpp.h\"\n\n#include \u003ciostream\u003e\n#include \u003cchrono\u003e\n\nconcurrencpp::result\u003cvoid\u003e consume_shared_result(concurrencpp::shared_result\u003cint\u003e shared_result,\n    std::shared_ptr\u003cconcurrencpp::executor\u003e resume_executor) {\n    std::cout \u003c\u003c \"Awaiting shared_result to have a value\" \u003c\u003c std::endl;\n\n    const auto\u0026 async_value = co_await shared_result;\n    concurrencpp::resume_on(resume_executor);\n\n    std::cout \u003c\u003c \"In thread id \" \u003c\u003c std::this_thread::get_id() \u003c\u003c \", got: \" \u003c\u003c async_value \u003c\u003c \", memory address: \" \u003c\u003c \u0026async_value \u003c\u003c std::endl;\n}\n\nint main() {\n    concurrencpp::runtime runtime;\n    auto result = runtime.background_executor()-\u003esubmit([] {\n        std::this_thread::sleep_for(std::chrono::seconds(1));\n        return 100;\n    });\n\n    concurrencpp::shared_result\u003cint\u003e shared_result(std::move(result));\n    concurrencpp::result\u003cvoid\u003e results[8];\n\n    for (size_t i = 0; i \u003c 8; i++) {\n        results[i] = consume_shared_result(shared_result, runtime.thread_pool_executor());\n    }\n\n    std::cout \u003c\u003c \"Main thread waiting for all consumers to finish\" \u003c\u003c std::endl;\n\n    auto tpe = runtime.thread_pool_executor();\n    auto all_consumed = concurrencpp::when_all(tpe, std::begin(results), std::end(results)).run();\n    all_consumed.get();\n\n    std::cout \u003c\u003c \"All consumers are done, exiting\" \u003c\u003c std::endl;\n    return 0;\n}\n```\n\n### Termination in concurrencpp\nWhen the runtime object gets out of scope of `main`, it iterates each stored executor and calls its `shutdown` method. Trying to access the timer-queue or any executor will throw an `errors::runtime_shutdown` exception. When an executor shuts down, it clears its inner task queues, destroying un-executed `task` objects. If a task object stores a concurrencpp-coroutine, that coroutine is resumed inline and an `errors::broken_task` exception is thrown inside it. \nIn any case where  a `runtime_shutdown` or a `broken_task` exception is thrown, applications should terminate their current code-flow gracefully as soon as possible. Those exceptions should not be ignored.\nBoth `runtime_shutdown` and `broken_task` inherit from `errors::interrupted_task` base class, and this type can also be used in a `catch` clause to handle termination in a unified way.\n\n### Resume executors\nMany concurrencpp asynchronous actions require an instance of an executor as their *resume executor*. When an asynchronous action (implemented as a coroutine) can finish synchronously, it resumes immediately in the calling thread of execution. If the asynchronous action can't finish synchronously, it will be resumed when it finishes, inside the given resume-executor. \nFor example, `when_any` utility function requires an instance of a resume-executor as its first argument. `when_any` returns a `lazy_result` which becomes ready when at least one given result becomes ready. If one of the results is already ready at the moment of calling `when_any`, the calling coroutine is resumed synchronously in the calling thread of execution. If not, the calling coroutine will be resumed when at least of result is finished, inside the given resume-executor. \nResume executors are important because they mandate where coroutines are resumed in cases where it's not clear where a coroutine is supposed to be resumed (for example, in the case of `when_any` and `when_all`), or in cases where the asynchronous action is processed inside one of the concurrencpp workers, which are only used to process that specific action, and not application code.  \n\n### Utility functions\n\n#### `make_ready_result` function\n`make_ready_result` creates a ready result object from given arguments. Awaiting such result will cause the current coroutine to resume immediately.  `get` and `operator co_await` will return the constructed value. \n\n```cpp\n/*\n    Creates a ready result object by building \u003c\u003ctype\u003e\u003e from arguments\u0026\u0026... in-place.\n    Might throw any exception that the constructor\n    of type(std::forward\u003cargument_types\u003e(arguments)...) throws.\n    Might throw std::bad_alloc exception if fails to allocate memory.\n*/\ntemplate\u003cclass type, class ... argument_types\u003e\nresult\u003ctype\u003e make_ready_result(argument_types\u0026\u0026 ... arguments);\n\n/*\n    An overload for void type.\n    Might throw std::bad_alloc exception if fails to allocate memory.\n*/\nresult\u003cvoid\u003e make_ready_result();\n```\n#### `make_exceptional_result` function\n`make_exceptional_result` creates a ready result object from a given exception. Awaiting such result will cause the current coroutine to resume immediately.  `get` and `operator co_await` will re-throw the given exception.\n```cpp\n/*\n    Creates a ready result object from an exception pointer.\n    The returned result object will re-throw exception_ptr when calling get or await.\n    Throws std::invalid_argument if exception_ptr is null.\n    Might throw std::bad_alloc exception if fails to allocate memory.\n*/\ntemplate\u003cclass type\u003e\nresult\u003ctype\u003e make_exceptional_result(std::exception_ptr exception_ptr);\n\n/*\n    Overload. Similar to make_exceptional_result(std::exception_ptr),\n    but gets an exception object directly.\n    Might throw any exception that the constructor of exception_type(std::move(exception)) might throw. \n    Might throw std::bad_alloc exception if fails to allocate memory.\n*/\ntemplate\u003cclass type, class exception_type\u003e\nresult\u003ctype\u003e make_exceptional_result(exception_type exception);\n```\n#### `when_all` function\n\n`when_all` is a utility function that creates a lazy result object which becomes ready when all input results are completed. Awaiting this lazy result returns all input-result objects in a ready state, ready to be consumed.\n\n`when_all` function comes with three flavors - one that accepts a heterogeneous range of result objects, another that gets a pair of iterators to a range of result objects of the same type, and lastly an overload that accepts no results objects at all.  In the case of no input result objects  - the function returns a ready result object of an empty tuple.\n\nIf one of the passed result-objects is empty, an exception will be thrown. In this case, input-result objects are unaffected by the function and can be used again after the exception was handled. If all input result objects are valid, they are emptied by this function, and returned in a valid and ready state as the output result.   \nCurrently, `when_all` only accepts `result` objects.\n\nAll overloads accept a resume executor as their first parameter. When awaiting a result returned by `when_all`, the caller coroutine will be resumed by the given resume executor.  \n\n```cpp \n/*\n    Creates a result object that becomes ready when all the input results become ready.\n    Passed result objects are emptied and returned as a tuple.\n    Throws std::invalid_argument if any of the passed result objects is empty.\n    Might throw an std::bad_alloc exception if no memory is available.\n*/\ntemplate\u003cclass ... result_types\u003e\nlazy_result\u003cstd::tuple\u003ctypename std::decay\u003cresult_types\u003e::type...\u003e\u003e\n   when_all(std::shared_ptr\u003cexecutor_type\u003e resume_executor,\n              result_types\u0026\u0026 ... results);\n\n/*\n    Overload. Similar to when_all(result_types\u0026\u0026 ...) but receives a pair of iterators referencing a range.\n    Passed result objects are emptied and returned as a vector.\n    If begin == end, the function returns immediately with an empty vector.\n    Throws std::invalid_argument if any of the passed result objects is empty.\n    Might throw an std::bad_alloc exception if no memory is available.\n*/\ntemplate\u003cclass iterator_type\u003e\nlazy_result\u003cstd::vector\u003ctypename std::iterator_traits\u003citerator_type\u003e::value_type\u003e\u003e\n   when_all(std::shared_ptr\u003cexecutor_type\u003e resume_executor,\n               iterator_type begin, iterator_type end);\n\n/*\n    Overload. Returns a ready result object that doesn't monitor any asynchronous result.\n    Might throw an std::bad_alloc exception if no memory is available.\n*/\nlazy_result\u003cstd::tuple\u003c\u003e\u003e when_all(std::shared_ptr\u003cexecutor_type\u003e resume_executor);\n```\n#### `when_any` function\n\n`when_any` is a utility function that creates a lazy result object which becomes ready when at least one input result is completed. Awaiting this result will return a helper struct containing all input-result objects plus the index of the completed task. It could be that by the time of consuming the ready result, other results might have already completed asynchronously. Applications can call `when_any` repeatedly in order to consume ready results as they complete until all results are consumed.\n \n`when_any` function comes with only two flavors - one that accepts a heterogeneous range of result objects and another that gets a pair of iterators to a range of result-objects of the same type. Unlike `when_all`, there is no meaning in awaiting at least one task to finish when the range of results is completely empty. Hence, there is no overload with no arguments. Also, the overload of two iterators will throw an exception if those iterators reference an empty range (when `begin == end`).   \n\nIf one of the passed result-objects is empty, an exception will be thrown. In any case an exception is thrown, input-result objects are unaffected by the function and can be used again after the exception was handled. If all input result objects are valid, they are emptied by this function, and returned in a valid state as the output result.  \nCurrently, `when_any` only accepts `result` objects. \n\nAll overloads accept a resume executor as their first parameter. When awaiting a result returned by `when_any`, the caller coroutine will be resumed by the given resume executor.  \n\n```cpp\n/*\n    Helper struct returned from when_any.\n    index is the position of the ready result in results sequence.\n    results is either an std::tuple or an std::vector of the results that were passed to when_any.\n*/\ntemplate \u003cclass sequence_type\u003e\nstruct when_any_result {\n    std::size_t index;\n    sequence_type results;\n};\n\n/*\n    Creates a result object that becomes ready when at least one of the input results is ready.\n    Passed result objects are emptied and returned as a tuple.\n    Throws std::invalid_argument if any of the passed result objects is empty.\n    Might throw an std::bad_alloc exception if no memory is available.\n*/\ntemplate\u003cclass ... result_types\u003e\nlazy_result\u003cwhen_any_result\u003cstd::tuple\u003cresult_types...\u003e\u003e\u003e\n   when_any(std::shared_ptr\u003cexecutor_type\u003e resume_executor,\n              result_types\u0026\u0026 ... results);\n\n/*\n    Overload. Similar to when_any(result_types\u0026\u0026 ...) but receives a pair of iterators referencing a range.\n    Passed result objects are emptied and returned as a vector.\n    Throws std::invalid_argument if begin == end.\n    Throws std::invalid_argument if any of the passed result objects is empty.\n    Might throw an std::bad_alloc exception if no memory is available.\n*/\ntemplate\u003cclass iterator_type\u003e\nlazy_result\u003cwhen_any_result\u003cstd::vector\u003ctypename std::iterator_traits\u003citerator_type\u003e::value_type\u003e\u003e\u003e\n   when_any(std::shared_ptr\u003cexecutor_type\u003e resume_executor,\n              iterator_type begin, iterator_type end);\n```\n\n#### `resume_on` function\n`resume_on` returns an awaitable that suspends the current coroutine and resumes it inside given `executor`. This is an important function that makes sure a coroutine is running in the right executor. For example, applications might schedule a background task using the `background_executor` and await the returned result object. In this case, the awaiting coroutine will be resumed inside the background executor. A call to `resume_on` with another cpu-bound executor makes sure that cpu-bound lines of code will not run on the background executor once the background task is completed. \nIf a task is re-scheduled to run on another executor using `resume_on`, but that executor is shut down before it can resume the suspended task, that task is resumed immediately and an `erros::broken_task` exception is thrown. In this case, applications need to quite gracefully.  \n```cpp\n/*\n    Returns an awaitable that suspends the current coroutine and resumes it inside executor.\n    Might throw any exception that executor_type::enqueue throws.\n*/\ntemplate\u003cclass executor_type\u003e\nauto resume_on(std::shared_ptr\u003cexecutor_type\u003e executor);\n```\n\n### Timers and Timer queues\n\nconcurrencpp also provides timers and timer queues.\nTimers are objects that define asynchronous actions running on an executor within a well-defined interval of time.\nThere are three types of timers - *regular timers*, *onshot-timers* and *delay objects*.\n\nRegular timers have four properties that define them:\n\n1. Callable - a callable that will be scheduled to run as a task periodically.\n2. Executor - an executor that schedules the callable to run periodically.\n3. Due time - from the time of creation, the interval in milliseconds in which the callable will be scheduled to run for the first time.\n4. Frequency - from the time the callable is scheduled to run for the first time, the interval in milliseconds the callable will be scheduled to run periodically, until the timer is destructed or cancelled.\n\nLike other objects in concurrencpp, timers are a move only type that can be empty.\nWhen a timer is destructed or `timer::cancel` is called, the timer cancels its scheduled but not yet executed tasks. Ongoing tasks are uneffected. The timer callable must be thread safe. It is recommended to set the due time and the frequency of timers to a granularity of 50 milliseconds. \n\nA timer queue is a concurrencpp worker that manages a collection of timers and processes them in just one thread of execution. It is also the agent used to create new timers.\nWhen a timer deadline (whether it is the timer's due-time or frequency) has reached, the timer queue \"fires\" the timer by scheduling its callable to run on the associated executor as a task.\n\nJust like executors, timer queues also adhere to the RAII concept. When the runtime object gets out of scope, It shuts down the timer queue, cancelling all pending timers. After a timer queue has been shut down, any subsequent call to `make_timer`, `make_onshot_timer` and `make_delay_object` will throw an `errors::runtime_shutdown` exception.\nApplications must not try to shut down timer queues by themselves.\n\n#### `timer_queue` API:\n```cpp   \nclass timer_queue {\n    /*\n        Destroys this timer_queue.\n    */\n    ~timer_queue() noexcept;\n    \n    /*\n        Shuts down this timer_queue:\n        Tells the underlying thread of execution to quit and joins it.\n        Cancels all pending timers.\n        After this call, invocation of any method besides shutdown\n        and shutdown_requested will throw an errors::runtime_shutdown.\n        If shutdown had been called before, this method has no effect.\n    */\n    void shutdown() noexcept;\n\n    /*\n        Returns true if shutdown had been called before, false otherwise.\n    */\n    bool shutdown_requested() const noexcept;\n\n    /*\n        Creates a new running timer where *this is the associated timer_queue.\n        Throws std::invalid_argument if executor is null.\n        Throws errors::runtime_shutdown if shutdown had been called before.\n        Might throw std::bad_alloc if fails to allocate memory.\n        Might throw std::system_error if the one of the underlying synchronization primitives throws.\n    */\n    template\u003cclass callable_type, class ... argumet_types\u003e\n    timer make_timer(\n        std::chrono::milliseconds due_time,\n        std::chrono::milliseconds frequency,\n        std::shared_ptr\u003cconcurrencpp::executor\u003e executor,\n        callable_type\u0026\u0026 callable,\n        argumet_types\u0026\u0026 ... arguments);\n\n    /*\n        Creates a new one-shot timer where *this is the associated timer_queue.\n        Throws std::invalid_argument if executor is null.\n        Throws errors::runtime_shutdown if shutdown had been called before.\n        Might throw std::bad_alloc if fails to allocate memory.\n        Might throw std::system_error if the one of the underlying synchronization primitives throws.\n    */\n    template\u003cclass callable_type, class ... argumet_types\u003e\n    timer make_one_shot_timer(\n        std::chrono::milliseconds due_time,\n        std::shared_ptr\u003cconcurrencpp::executor\u003e executor,\n        callable_type\u0026\u0026 callable,\n        argumet_types\u0026\u0026 ... arguments);\n\n    /*\n        Creates a new delay object where *this is the associated timer_queue.\n        Throws std::invalid_argument if executor is null.\n        Throws errors::runtime_shutdown if shutdown had been called before.\n        Might throw std::bad_alloc if fails to allocate memory.\n        Might throw std::system_error if the one of the underlying synchronization primitives throws.\n    */\n    result\u003cvoid\u003e make_delay_object(\n        std::chrono::milliseconds due_time,\n        std::shared_ptr\u003cconcurrencpp::executor\u003e executor);\n};\n```\n\n#### `timer` API:\n\n```cpp   \nclass timer {\n    /*\n        Creates an empty timer.\n    */\n    timer() noexcept = default;\n\n    /*\n        Cancels the timer, if not empty.\n    */\n    ~timer() noexcept;\n\n    /*\n        Moves the content of rhs to *this.\n        rhs is empty after this call.\n    */\n    timer(timer\u0026\u0026 rhs) noexcept = default;\n\n    /*\n        Moves the content of rhs to *this.\n        rhs is empty after this call.\n        Returns *this.\n    */\n    timer\u0026 operator = (timer\u0026\u0026 rhs) noexcept;\n\n    /*\n        Cancels this timer.\n        After this call, the associated timer_queue will not schedule *this\n        to run again and *this becomes empty.\n        Scheduled, but not yet executed tasks are cancelled.\n        Ongoing tasks are uneffected.\n        This method has no effect if *this is empty or the associated timer_queue has already expired.\n        Might throw std::system_error if one of the underlying synchronization primitives throws.\n    */\n    void cancel();\n\n    /*\n        Returns the associated executor of this timer.    \n        Throws concurrencpp::errors::empty_timer is *this is empty.\n    */\n    std::shared_ptr\u003cexecutor\u003e get_executor() const;\n\n    /*\n        Returns the associated timer_queue of this timer.\n        Throws concurrencpp::errors::empty_timer is *this is empty.\n    */\n    std::weak_ptr\u003ctimer_queue\u003e get_timer_queue() const;\n\n    /*\n        Returns the due time of this timer.\n        Throws concurrencpp::errors::empty_timer is *this is empty.\n    */\n    std::chrono::milliseconds get_due_time() const;\n\n    /*\n        Returns the frequency of this timer.    \n        Throws concurrencpp::errors::empty_timer is *this is empty.\n    */\n    std::chrono::milliseconds get_frequency() const;\n\n    /*\n        Sets new frequency for this timer.\n        Callables already scheduled to run at the time of invocation are not affected.    \n        Throws concurrencpp::errors::empty_timer is *this is empty.\n    */\n    void set_frequency(std::chrono::milliseconds new_frequency);\n\n    /*\n        Returns true is *this is not an empty timer, false otherwise.\n        The timer should not be used if this-\u003eoperator bool() is false.\n    */\n   explicit operator bool() const noexcept;\n};\n```\n\n#### Regular timer example:\n\nIn this example we create a regular timer by using the timer queue. The timer schedules its callable to run after 1.5 seconds, then fires its callable every 2 seconds. The given callable runs on the threadpool executor.\n\n```cpp\n#include \"concurrencpp/concurrencpp.h\"\n\n#include \u003ciostream\u003e\n\nusing namespace std::chrono_literals;\n\nint main() {\n    concurrencpp::runtime runtime;\n    std::atomic_size_t counter = 1;\n    concurrencpp::timer timer = runtime.timer_queue()-\u003emake_timer(\n        1500ms,\n        2000ms,\n        runtime.thread_pool_executor(),\n        [\u0026] {\n            const auto c = counter.fetch_add(1);\n            std::cout \u003c\u003c \"timer was invoked for the \" \u003c\u003c c \u003c\u003c \"th time\" \u003c\u003c std::endl;\n        });\n\n    std::this_thread::sleep_for(12s);\n    return 0;\n}\n```\n\n#### Oneshot timers\n\nA oneshot timer is a one-time timer with only a due time - after it schedules its callable to run once it never reschedules it to run again.  \n\n#### Oneshot timer example:\n\nIn this example, we create a timer that runs only once - after 3 seconds from its creation, the timer will schedule its callable to run on a new thread of execution (using `thread_executor`).\n\n```cpp\n#include \"concurrencpp/concurrencpp.h\"\n\n#include \u003ciostream\u003e\n\nusing namespace std::chrono_literals;\n\nint main() {\n    concurrencpp::runtime runtime;\n    concurrencpp::timer timer = runtime.timer_queue()-\u003emake_one_shot_timer(\n        3000ms,\n        runtime.thread_executor(),\n        [\u0026] {\n            std::cout \u003c\u003c \"hello and goodbye\" \u003c\u003c std::endl;\n        });\n\n    std::this_thread::sleep_for(4s);\n    return 0;\n}\n```\n\n#### Delay objects\n\nA delay object is a lazy result object that becomes ready when it's `co_await`ed and its due time is reached. Applications can `co_await` this result object to delay the current coroutine in a non-blocking way.  The current coroutine is resumed by the executor that was passed to `make_delay_object`.\n\n#### Delay object example:\n\nIn this example, we spawn a task (that does not return any result or thrown exception), which delays itself in a loop by calling `co_await` on a delay object.\n\n```cpp\n#include \"concurrencpp/concurrencpp.h\"\n\n#include \u003ciostream\u003e\n\nusing namespace std::chrono_literals;\n\nconcurrencpp::null_result delayed_task(\n    std::shared_ptr\u003cconcurrencpp::timer_queue\u003e tq,\n    std::shared_ptr\u003cconcurrencpp::thread_pool_executor\u003e ex) {\n    size_t counter = 1;\n\n    while(true) {\n        std::cout \u003c\u003c \"task was invoked \" \u003c\u003c counter \u003c\u003c \" times.\" \u003c\u003c std::endl;\n        counter++;\n\n        co_await tq-\u003emake_delay_object(1500ms, ex);\n    }\n}\n\nint main() {\n    concurrencpp::runtime runtime;\n    delayed_task(runtime.timer_queue(), runtime.thread_pool_executor());\n\n    std::this_thread::sleep_for(10s);\n    return 0;\n}\n```\n\n### Generators \nA generator is a lazy, synchronous coroutine that is able to produce a stream of values to consume. Generators use the `co_yield` keyword to yield values back to their consumers.\n\nGenerators are meant to be used synchronously - they can only use the `co_yield` keyword and **must not** use the `co_await` keyword. A generator will continue  to produce values as long as the `co_yield` keyword is called. \nIf the `co_return` keyword is called (explicitly or implicitly), then the generator will stop producing values.  Similarly, if an exception is thrown then the generator will stop producing values and the thrown exception will be re-thrown to the consumer of the generator.\n\nGenerators are meant to be used in a `range-for` loop: Generators implicitly produce two iterators - `begin` and `end` which control the execution of the `for` loop. These iterators should not be handled or accessed manually.\n\nWhen a generator is created, it starts as a lazy task. When its `begin` method is called, the generator is resumed for the first time and an iterator is returned. The lazy task is resumed repeatedly by calling `operator++` on the returned iterator. The returned iterator will be equal to `end` iterator when the generator finishes execution either by exiting gracefully or throwing an exception.  As mentioned earlier, this happens behind the scenes by the inner mechanism of the loop and the generator, and should not be called directly.\n\nLike other objects in concurrencpp, Generators are a move-only type. After a generator was moved, it is considered empty and trying to access its inner methods (other than `operator bool`) will throw an exception. The emptiness of a generator should not generally occur - it is advised to consume generators upon their creation in a `for` loop and not to try to call their methods individually. \n\n#### `generator` API\n```cpp\nclass generator {\n    /*\n        Move constructor. After this call, rhs is empty.\n    */\n    generator(generator\u0026\u0026 rhs) noexcept;\n\n    /*\n        Destructor. Invalidates existing iterators.\n    */\n    ~generator() noexcept;\n\n    generator(const generator\u0026 rhs) = delete;\n    generator\u0026 operator=(generator\u0026\u0026 rhs) = delete;\n    generator\u0026 operator=(const generator\u0026 rhs) = delete;\n    \n    /*\n        Returns true if this generator is not empty.\n        Applications must not use this object if this-\u003eoperator bool() is false.\n    */\n    explicit operator bool() const noexcept;\n\n    /*\n        Starts running this generator and returns an iterator.\n        Throws errors::empty_generator if *this is empty.\n        Re-throws any exception that is thrown inside the generator code.\n    */\n    iterator begin();\n\n    /*\n        Returns an end iterator.\n    */\n    static generator_end_iterator end() noexcept;\n};\n\nclass generator_iterator {\n  \n    using value_type = std::remove_reference_t\u003ctype\u003e;\n    using reference = value_type\u0026;\n    using pointer = value_type*;\n    using iterator_category = std::input_iterator_tag;\n    using difference_type = std::ptrdiff_t;\n\n    /*\n        Resumes the suspended generator and returns *this.\n        Re-throws any exception that was thrown inside the generator code.\n    */\n    generator_iterator\u0026 operator++();\n\n    /*\n        Post-increment version of operator++. \n    */\n    void operator++(int);\n\n    /*\n        Returns the latest value produced by the associated generator.\n    */\n    reference operator*() const noexcept;\n\t  \n    /*\n        Returns a pointer to the latest value produced by the associated generator. \n    */\n    pointer operator-\u003e() const noexcept;\n\n    /*\n        Comparision operators. \n    */\n    friend bool operator==(const generator_iterator\u0026 it0, const generator_iterator\u0026 it1) noexcept;\n    friend bool operator==(const generator_iterator\u0026 it, generator_end_iterator) noexcept;\n    friend bool operator==(generator_end_iterator end_it, const generator_iterator\u0026 it) noexcept;\n    friend bool operator!=(const generator_iterator\u0026 it, generator_end_iterator end_it) noexcept;\n    friend bool operator!=(generator_end_iterator end_it, const generator_iterator\u0026 it) noexcept;\n};\n```    \n#### `generator` example: \n\nIn this example, we will write a generator that yields the n-th member of the Sequence `S(n) = 1 + 2 + 3 + ... + n`  where `n \u003c= 100`:\n\n```cpp\nconcurrencpp::generator\u003cint\u003e sequence() {\n    int i = 1;\n    int sum = 0;\n    while (i \u003c= 100) {\n        sum += i;\n        ++i;\n        co_yield sum;\n    }\n}\n\nint main() {\n    for (auto value : sequence()) {\n        std::cout \u003c\u003c value \u003c\u003c std::end;\n    }\n    return 0;\n} \n```\n\n### Asynchronous locks\nRegular synchronous locks cannot be used safely inside tasks for a number of reasons:\n\n - Synchronous locks, such as `std::mutex`, are expected to be locked and unlocked in the same thread of execution. Unlocking a synchronous lock in a thread which had not locked it is undefined behavior. Since tasks can be suspended and resumed in any thread of execution, synchronous locks will break when used inside tasks.\n - Synchronous locks were created to work with *threads* and not with *coroutines*. If a synchronous lock is already locked by one thread, then when another thread tries to lock it, the entire thread of execution will be blocked and will be unblocked when the lock is released. This mechanism works well for traditional multi-threading paradigms but not for coroutines: with coroutines, we want *tasks* to be *suspended and resumed* without blocking or interfering with the execution of underlying threads and executors.    \n\n  `concurrencpp::async_lock` solves those issues by providing a similar API to `std::mutex`, with the main difference that calls to `concurrencpp::async_lock` will return a lazy-result that can be `co_awaited` safely inside tasks.  If one task tries to lock an async-lock and fails, the task will be suspended, and will be resumed when the lock is unlocked and acquired by the suspended task. This allows executors to process a huge amount of tasks waiting to acquire a lock without expensive context-switching and expensive kernel calls. \n\nSimilar to how `std::mutex` works, only one task can acquire `async_lock` at any given time, and a *read barrier* is place at the moment of acquiring. Releasing an async lock places a *write barrier* and allows the next task to acquire it, creating a chain of one-modifier at a time which sees the changes other modifiers had done and posts its modifications for the next modifiers to see.    \n\nLike `std::mutex`, `concurrencpp::async_lock` ***is not recursive***. Extra attention must be given when acquiring such lock - A lock must not be acquired again in a task that has been spawned by another task which had already acquired the lock. In such case, an unavoidable dead-lock will occur.  Unlike other objects in concurrencpp, `async_lock` is neither copiable nor movable. \n\nLike standard locks, `concurrencpp::async_lock` is meant to be used with scoped wrappers which leverage C++ RAII idiom to ensure locks are always unlocked upon  function return or thrown exception. `async_lock::lock` returns a lazy-result of a scoped wrapper that calls `async_lock::unlock` on destruction. Raw uses of `async_lock::unlock` are discouraged. `concurrencpp::scoped_async_lock` acts as the scoped wrapper and provides an API which is almost identical to `std::unique_lock`. `concurrencpp::scoped_async_lock` is movable, but not copiable.\n\n`async_lock::lock` and `scoped_async_lock::lock` require a resume-executor as their parameter. Upon calling those methods, if the lock is available for locking, then it is locked and the current task is resumed immediately. If not, then the current task is suspended, and will be resumed inside the given resume-executor when the lock is finally acquired. \n\n`concurrencpp::scoped_async_lock` wraps an `async_lock` and ensure it's properly unlocked. like `std::unique_lock`, there are cases it does not wrap any lock, and in this case it's considered to be empty.  An empty  `scoped_async_lock` can happen when it's defaultly constructed, moved, or `scoped_async_lock::release` method is called. An empty scoped-async-lock will not unlock any lock on destruction. \n\nEven if the scoped-async-lock is not empty, it does not mean that it owns the underlying async-lock and it will unlock it on destruction. Non-empty and non-owning scoped-async locks can happen if `scoped_async_lock::unlock` was called or the scoped-async-lock was constructed using `scoped_async_lock(async_lock\u0026, std::defer_lock_t)` constructor.\n\n#### `async_lock` API\n```cpp\nclass async_lock {\n    /*\n        Constructs an async lock object.\n    */\n    async_lock() noexcept;\n\t\n    /*\n        Destructs an async lock object.\n        *this is not automatically unlocked at the moment of destruction. \n    */\t\n    ~async_lock() noexcept;\n\t\n    /*\n        Asynchronously acquires the async lock. \n        If *this has already been locked by another non-parent task, the current task will be suspended\n        and will be resumed when *this is acquired, inside resume_executor.\n        If *this has not been locked by another task, then *this will be acquired and the current task will be resumed \n        immediately in the calling thread of execution.\n        If *this has already been locked by a parent task, then unavoidable dead-lock will occur.\n        Throws std::invalid_argument if resume_executor is null.\n        Throws std::system error if one of the underlying synhchronization primitives throws.\t\n    */\n    lazy_result\u003cscoped_async_lock\u003e lock(std::shared_ptr\u003cexecutor\u003e resume_executor);\n       \n    /*\n        Tries to acquire *this in the calling thread of execution.\n        Returns true if *this is acquired, false otherwise.\n        In any case, the current task is resumed immediately in the calling thread of execution.\n        Throws std::system error if one of the underlying synhchronization primitives throws.\n    */\n    lazy_result\u003cbool\u003e try_lock();\n       \n    /*\n        Releases *this and allows other tasks (including suspended tasks waiting for *this) to acquire it.\n        Throws std::system error if *this is not locked at the moment of calling this method.\n        Throws std::system error if one of the underlying synhchronization primitives throws.\t\n    */\n    void unlock();\n};\n```\n#### `scoped_async_lock` API\n\n```cpp\nclass scoped_async_lock {\n    /*\n        Constructs an async lock wrapper that does not wrap any async lock.\n    */\n    scoped_async_lock() noexcept = default;\n\t\n    /*\n        If *this wraps async_lock, this method releases the wrapped lock.\n    */\n    ~scoped_async_lock() noexcept;\n\n    /*\n        Moves rhs to *this.\n        After this call, *rhs does not wrap any async lock.\n    */\n    scoped_async_lock(scoped_async_lock\u0026\u0026 rhs) noexcept;\n\n    /*\n        Wrapps unlocked lock.\n        lock must not be in acquired mode when calling this method.\n    */\n    scoped_async_lock(async_lock\u0026 lock, std::defer_lock_t) noexcept;\n\t\t\n    /*\n        Wrapps locked lock.\n        lock must be already acquired when calling this method.\n    */\n    scoped_async_lock(async_lock\u0026 lock, std::adopt_lock_t) noexcept;\n\n    /*\n        Calls async_lock::lock on the wrapped locked, using resume_executor as a parameter.\n        Throws std::invalid_argument if resume_executor is nulll.\n        Throws std::system_error if *this does not wrap any lock.\n        Throws std::system_error if wrapped lock is already locked.\n        Throws any exception async_lock::lock throws.\n    */\n    lazy_result\u003cvoid\u003e lock(std::shared_ptr\u003cexecutor\u003e resume_executor);\n\t\n    /*\n        Calls async_lock::try_lock on the wrapped lock.\n        Throws std::system_error if *this does not wrap any lock.\n        Throws std::system_error if wrapped lock is already locked.\n        Throws any exception async_lock::try_lock throws.\n    */\n    lazy_result\u003cbool\u003e try_lock();\n\t\n    /*\n        Calls async_lock::unlock on the wrapped lock.\n        If *this does not wrap any lock, this method does nothing.\n        Throws std::system_error if *this wraps a lock and it is not locked.\n    */\n    void unlock();\n\n    /*\n        Checks whether *this wraps a locked mutex or not.\n        Returns true if wrapped locked is in acquired state, false otherwise.\n    */\n    bool owns_lock() const noexcept;\n\n    /*\n        Equivalent to owns_lock.\n    */\n    explicit operator bool() const noexcept;\n\n    /*\n        Swaps the contents of *this and rhs.\n    */\n    void swap(scoped_async_lock\u0026 rhs) noexcept;\n\t\n    /*\n        Empties *this and returns a pointer to the previously wrapped lock.\n        After a call to this method, *this doesn't wrap any lock.\t\t\t\n        The previously wrapped lock is not released, \n        it must be released by either unlocking it manually through the returned pointer or by \n        capturing the pointer with another scoped_async_lock which will take ownerwhip over it.\n    */\n    async_lock* release() noexcept;\n\t\n    /*\n        Returns a pointer to the wrapped async_lock, or a null pointer if there is no wrapped async_lock. \n    */\n    async_lock* mutex() const noexcept;\n};\n```\n#### `async_lock` example:\n\nIn this example we push 10,000,000 integers to an `std::vector` object from different tasks concurrently, while using `async_lock` to make sure no data race occurs and the correctness of the internal state of that vector object is preserved.   \n\n```cpp\n#include \"concurrencpp/concurrencpp.h\"\n\n#include \u003cvector\u003e\n#include \u003ciostream\u003e\n\nstd::vector\u003csize_t\u003e numbers;\nconcurrencpp::async_lock lock;\n\nconcurrencpp::result\u003cvoid\u003e add_numbers(concurrencpp::executor_tag,\n                                       std::shared_ptr\u003cconcurrencpp::executor\u003e executor,\n                                       size_t begin,\n                                       size_t end) {\n    for (auto i = begin; i \u003c end; i++) {\n        concurrencpp::scoped_async_lock raii_wrapper = co_await lock.lock(executor);\n        numbers.push_back(i);\n    }\n}\n\nint main() {\n    concurrencpp::runtime runtime;\n    constexpr size_t range = 10'000'000;\n    constexpr size_t sections = 4;\n    concurrencpp::result\u003cvoid\u003e results[sections];\n\n    for (size_t i = 0; i \u003c 4; i++) {\n        const auto range_start = i * range / sections;\n        const auto range_end = (i + 1) * range / sections;\n\n        results[i] = add_numbers({}, runtime.thread_pool_executor(), range_start, range_end);\n    }\n\n    for (auto\u0026 result : results) {\n        result.get();\n    }\n\n    std::cout \u003c\u003c \"vector size is \" \u003c\u003c numbers.size() \u003c\u003c std::endl;\n\n    // make sure the vector state has not been corrupted by unprotected concurrent accesses\n    std::sort(numbers.begin(), numbers.end());\n    for (size_t i = 0; i \u003c range; i++) {\n        if (numbers[i] != i) {\n            std::cerr \u003c\u003c \"vector state is corrupted.\" \u003c\u003c std::endl;\n            return -1;\n        }\n    }\n\n    std::cout \u003c\u003c \"succeeded pushing range [0 - 10,000,000] concurrently to the vector!\" \u003c\u003c std::endl;\n    return 0;\n}\n```\n\n### Asynchronous condition variables\n\n`async_condition_variable` imitates the standard `condition_variable` and can be used safely with tasks alongside `async_lock`. `async_condition_variable` works with `async_lock` to suspend a task until some shared memory (protected by the lock) has changed. Tasks that want to monitor shared memory changes will lock an instance of `async_lock`, and call `async_condition_variable::await`.  This will atomically unlock the lock and suspend the current task until some modifier task notifies the condition variable. A modifier task acquires the lock, modifies the shared memory, unlocks the lock and call either `notify_one` or `notify_all`.\nWhen a suspended task is resumed (using the resume executor that was given to `await`), it locks the lock again, allowing the task to continue from the point of suspension seamlessly.\nLike `async_lock`, `async_condition_variable` is neither movable or copiable - it is meant to be created in one place and accessed by multiple tasks.\n\n`async_condition_variable::await` overloads require a resume-executor, which will be used to resume the task, and a locked `scoped_async_lock`. `async_condition_variable::await` comes with two overloads - one that accepts a predicate and one that doesn't. The overload which does not accept a predicate will suspend the calling task immediately upon invocation until it's resumed by a call to `notify_*`. The overload which does accept a predicate works by letting the predicate inspect the shared memory and suspend the task repeatedly until the shared memory has reached its wanted state. schematically it works like calling \n\n```cpp\nwhile (!pred()) { // pred() inspects the shared memory and returns true or false\n\tco_await await(resume_executor, lock); // suspend the current task until another task calls `notify_xxx`\n}\n```\nJust like the standard condition variable, applications are encouraged to use the predicate-overload, as it allows more fine-grained control over suspensions and resumptions.\n`async_condition_variable` can be used to write concurrent collections and data-structures like concurrent queues and channels.\n\nInternally, `async_condition_variable` holds a suspension-queue, in which tasks enqueue themselves when they await the condition variable to be notified. When any of `notify_*` methods are called, the notifying task dequeues either one task or all of the tasks, depending on the invoked method. Tasks are dequeued from the suspension-queue in a fifo manner. \nFor example, if Task A calls `await` and then Task B calls `await`, then Task C calls `notify_one`, then internally task A will be dequeued and and resumed. Task B will remain suspended until another call to `notify_one` or `notify_all` is called. If task A and task B are suspended and task C calls `notify_all`, then both tasks will be dequeued and resumed. \n\n#### `async_condition_variable` API\n```cpp\nclass async_condition_variable {\n\t/*\n\t\tConstructor.\n\t*/\n\tasync_condition_variable() noexcept;\n\n\t/*\n\t\tAtomically releases lock and suspends the current task by adding it to *this suspension-queue.\n\t\tThrows std::invalid_argument if resume_executor is null.\n\t\tThrows std::invalid_argument if lock is not locked at the moment of calling this method.\n\t\tMight throw std::system_error if the underlying std::mutex throws. \n\t*/\n\tlazy_result\u003cvoid\u003e await(std::shared_ptr\u003cexecutor\u003e resume_executor, scoped_async_lock\u0026 lock);\n\n\t/*\n\t\tEquivalent to:\t\t\n\t\twhile (!pred()) {\n            co_await await(resume_executor, lock);\n        }\n\t\t\n\t\tMight throw any exception that await(resume_executor, lock) might throw.\n\t\tMight throw any exception that pred might throw.\n\t*/\n\ttemplate\u003cclass predicate_type\u003e\n\tlazy_result\u003cvoid\u003e await(std::shared_ptr\u003cexecutor\u003e resume_executor, scoped_async_lock\u0026 lock, predicate_type pred);\n\t\n\t/*\n\t\tDequeues one task from *this suspension-queue and resumes it, if any available at the moment of calling this method.\n\t\tThe suspended task is resumed by scheduling it to run on the executor given when await was called.\n\t\tMight throw std::system_error if the underlying std::mutex throws. \n\t*/\n\tvoid notify_one();\n\t\n\t/*\n\t\tDequeues all tasks from *this suspension-queue and resumes them, if any available at the moment of calling this method.\n\t\tThe suspended tasks are resumed by scheduling them to run on the executors given when await was called.\n\t\tMight throw std::system_error if the underlying std::mutex throws. \n\t*/\n\tvoid notify_all();\n};\n```\n\n#### `async_condition_variable` example:\n\nIn this example, `async_lock` and `async_condition_variable` work together to implement a concurrent queue that can be used to send data (in this example, integers) between tasks. Note that some methods return a `result` while another return `lazy_result`, showing how both eager and lazy tasks can work together.\n\n```cpp\n#include \"concurrencpp/concurrencpp.h\"\n\n#include \u003cqueue\u003e\n#include \u003ciostream\u003e\n\nusing namespace concurrencpp;\n\nclass concurrent_queue {\n\n   private:\n    async_lock _lock;\n    async_condition_variable _cv;\n    std::queue\u003cint\u003e _queue;\n    bool _abort = false;\n\n   public:\n    concurrent_queue() = default;\n\n    result\u003cvoid\u003e shutdown(std::shared_ptr\u003cexecutor\u003e resume_executor) {\n        {\n            auto guard = co_await _lock.lock(resume_executor);\n            _abort = true;\n        }\n\n        _cv.notify_all();\n    }\n\n    lazy_result\u003cvoid\u003e push(std::shared_ptr\u003cexecutor\u003e resume_executor, int i) {\n        {\n            auto guard = co_await _lock.lock(resume_executor);\n            _queue.push(i);\n        }\n\n        _cv.notify_one();\n    }\n\n    lazy_result\u003cint\u003e pop(std::shared_ptr\u003cexecutor\u003e resume_executor) {\n        auto guard = co_await _lock.lock(resume_executor); \n        co_await _cv.await(resume_executor, guard, [this] {\n            return _abort || !_queue.empty();\n        });\n\n        if (!_queue.empty()) {\n            auto result = _queue.front();\n            _queue.pop();\n\n            co_return result;        \n        }\n\n        assert(_abort);\n        throw std::runtime_error(\"queue has been shut down.\");\n    }\n};\n\nresult\u003cvoid\u003e producer_loop(executor_tag,\n                           std::shared_ptr\u003cthread_pool_executor\u003e tpe,\n                           concurrent_queue\u0026 queue,\n                           int range_start,\n                           int range_end) {\n    for (; range_start \u003c range_end; ++range_start) {\n        co_await queue.push(tpe, range_start);\n    }\n}\n\nresult\u003cvoid\u003e consumer_loop(executor_tag, std::shared_ptr\u003cthread_pool_executor\u003e tpe, concurrent_queue\u0026 queue) {\n    try {\n        while (true) {\n            std::cout \u003c\u003c co_await queue.pop(tpe) \u003c\u003c std::endl;\n        }\n    } catch (const std::exception\u0026 e) {\n        std::cerr \u003c\u003c e.what() \u003c\u003c std::endl;\n    }\n}\n\nint main() {\n    runtime runtime;\n    const auto thread_pool_executor = runtime.thread_pool_executor();\n    concurrent_queue queue;\n\n    result\u003cvoid\u003e producers[4];\n    result\u003cvoid\u003e consumers[4];\n\n    for (int i = 0; i \u003c 4; i++) {\n        producers[i] = producer_loop({}, thread_pool_executor, queue, i * 5, (i + 1) * 5);\n    }\n\n    for (int i = 0; i \u003c 4; i++) {\n        consumers[i] = consumer_loop({}, thread_pool_executor, queue);\n    }\n\n    for (int i = 0; i \u003c 4; i++) {\n        producers[i].get();\n    }\n\n    queue.shutdown(thread_pool_executor).get();\n\n    for (int i = 0; i \u003c 4; i++) {\n        consumers[i].get();\n    }\n\n    return 0;\n}\n```\n\n\n### The runtime object\n \nThe concurrencpp runtime object is the agent used to acquire, store and create new executors.  \nThe runtime must be created as a value type as soon as the main function starts to run.\nWhen the concurrencpp runtime gets out of scope, it iterates over its stored executors and shuts them down one by one by calling `executor::shutdown`. Executors then exit their inner work loop and any subsequent attempt to schedule a new task will throw a `concurrencpp::runtime_shutdown` exception. The runtime also contains the global timer queue used to create timers and delay objects.\nUpon destruction, stored executors destroy unexecuted tasks, and wait for ongoing tasks to finish. If an ongoing task tries to use an executor to spawn new tasks or schedule its own task continuation - an exception will be thrown. In this case, ongoing tasks need to quit as soon as possible, allowing their underlying executors to quit. The timer queue will also be shut down, cancelling all running timers.  With this RAII style of code, no tasks can be processed before the creation of the runtime object, and while/after the runtime gets out of scope.\nThis frees concurrent applications from needing to communicate termination messages explicitly. Tasks are free use executors as long as the runtime object is alive.\n\n#### `runtime` API\n\n```cpp\nclass runtime {\n    /*\n        Creates a runtime object with default options.    \n    */\n    runtime();\n\n    /*\n        Creates a runtime object with user defined options.\n    */\n    runtime(const concurrencpp::runtime_options\u0026 options);\n\n    /*\n        Destroys this runtime object.\n        Calls executor::shutdown on each monitored executor.\n        Calls timer_queue::shutdown on the global timer queue.\n    */\n    ~runtime() noexcept;\n\n    /*\n        Returns this runtime timer queue used to create new times.\n    */\n    std::shared_ptr\u003cconcurrencpp::timer_queue\u003e timer_queue() const noexcept;\n\n    /*\n        Returns this runtime concurrencpp::inline_executor\n    */\n    std::shared_ptr\u003cconcurrencpp::inline_executor\u003e inline_executor() const noexcept;\n\n    /*\n        Returns this runtime concurrencpp::thread_pool_executor\n    */\n    std::shared_ptr\u003cconcurrencpp::thread_pool_executor\u003e thread_pool_executor() const noexcept;\n\n    /*\n        Returns this runtime concurrencpp::background_executor\n    */\n    std::shared_ptr\u003cconcurrencpp::thread_pool_executor\u003e background_executor() const noexcept;\n\n    /*\n        Returns this runtime concurrencpp::thread_executor\n    */\n    std::shared_ptr\u003cconcurrencpp::thread_executor\u003e thread_executor() const noexcept;\n\n    /*\n        Creates a new concurrencpp::worker_thread_executor and registers it in this runtime.\n        Might throw std::bad_alloc or std::system_error if any underlying memory or system resource could not have been acquired.\n    */\n    std::shared_ptr\u003cconcurrencpp::worker_thread_executor\u003e make_worker_thread_executor();\n\n    /*\n        Creates a new concurrencpp::manual_executor and registers it in this runtime.\n        Might throw std::bad_alloc or std::system_error if any underlying memory or system resource could not have been acquired.\n    */\n    std::shared_ptr\u003cconcurrencpp::manual_executor\u003e make_manual_executor();\n\n    /*\n        Creates a new user defined executor and registers it in this runtime.\n        executor_type must be a valid concrete class of concurrencpp::executor.\n        Might throw std::bad_alloc if no memory is available.\n        Might throw any exception that the constructor of \u003c\u003cexecutor_type\u003e\u003e might throw.\n    */\n    template\u003cclass executor_type, class ... argument_types\u003e\n    std::shared_ptr\u003cexecutor_type\u003e make_executor(argument_types\u0026\u0026 ... arguments);\n\n    /*\n        returns the version of concurrencpp that the library was built with.\n    */\n    static std::tuple\u003cunsigned int, unsigned int, unsigned int\u003e version() noexcept;\n};\n```\n#### Thread creation and termination monitoring\n\nIn some cases, applications are interested in monitoring thread creation and termination, for example, some memory allocators require new threads to be registered and unregistered upon their creation and termination.  The concurrencpp runtime allows setting a thread creation callback and a thread termination callback. those callbacks will be called whenever one of the concurrencpp workers create a new thread and when that thread is terminating. Those callbacks are always called from inside the created/terminating thread, so `std::this_thread::get_id` will always return the relevant thread ID.  The signature of those callbacks is `void callback (std::string_view thread_name)`. `thread_name` is a concurrencpp specific title that is given to the thread and can be observed in some debuggers that present the thread name. The thread name is not guaranteed to be unique and should be used for logging and debugging. \n\nIn order to set a thread-creation callback and/or a thread termination callback, applications can set the `thread_started_callback` and/or `thread_terminated_callback` members of the `runtime_options` which is passed to the runtime constructor. Since those callbacks are copied to each concurrencpp worker that might create threads, those callbacks have to be copiable.    \n\n#### Example: monitoring thread creation and termination\n\n```cpp\n#include \"concurrencpp/concurrencpp.h\"\n\n#include \u003ciostream\u003e\n\nint main() {\n    concurrencpp::runtime_options options;\n    options.thread_started_callback = [](std::string_view thread_name) {\n        std::cout \u003c\u003c \"A new thread is starting to run, name: \" \u003c\u003c thread_name \u003c\u003c \", thread id: \" \u003c\u003c std::this_thread::get_id()\n                  \u003c\u003c std::endl;\n    };\n\n    options.thread_terminated_callback = [](std::string_view thread_name) {\n        std::cout \u003c\u003c \"A thread is terminating, name: \" \u003c\u003c thread_name \u003c\u003c \", thread id: \" \u003c\u003c std::this_thread::get_id() \u003c\u003c std::endl;\n    };\n\n    concurrencpp::runtime runtime(options);\n    const auto timer_queue = runtime.timer_queue();\n    const auto thread_pool_executor = runtime.thread_pool_executor();\n\n    concurrencpp::timer timer =\n        timer_queue-\u003emake_timer(std::chrono::milliseconds(100), std::chrono::milliseconds(500), thread_pool_executor, [] {\n            std::cout \u003c\u003c \"A timer callable is executing\" \u003c\u003c std::endl;\n        });\n\n    std::this_thread::sleep_for(std::chrono::seconds(3));\n\n    return 0;\n}\n```\nPossible output:\n\n```\nA new thread is starting to run, name: concurrencpp::timer_queue worker, thread id: 7496\nA new thread is starting to run, name: concurrencpp::thread_pool_executor worker, thread id: 21620\nA timer callable is executing\nA timer callable is executing\nA timer callable is executing\nA timer callable is executing\nA timer callable is executing\nA timer callable is executing\nA thread is terminating, name: concurrencpp::timer_queue worker, thread id: 7496\nA thread is terminating, name: concurrencpp::thread_pool_executor worker, thread id: 21620\n```\n\n#### Creating user-defined executors\n\nApplications can create their own custom executor type by inheriting the `derivable_executor` class.\nThere are a few points to consider when implementing user defined executors:\nThe most important thing is to remember that executors are used from multiple threads, so implemented methods must be thread-safe.\n\nNew executors can be created using `runtime::make_executor`. Applications must not create new executors with plain instantiation (such as `std::make_shared` or plain `new`), only by using  `runtime::make_executor`. Also, applications must not try to re-instantiate the built-in concurrencpp executors, like the `thread_pool_executor` or the `thread_executor`, those executors must only be accessed through their existing instances in the runtime object.\n\nAnother important point is to handle shutdown correctly: `shutdown`, `shutdown_requested` and `enqueue` should all monitor the executor state and behave accordingly when invoked:\n* `shutdown` should tell underlying threads to quit and then join them.\n* `shutdown` might be called multiple times, and the method must handle this scenario by ignoring any subsequent calls to `shutdown` after the first invocation.\n* `enqueue` must throw a `concurrencpp::errors::runtime_shutdown` exception if `shutdown` had been called before.\n\n#### `task` objects\n \nImplementing executors is one of the rare cases where applications need to work with `concurrencpp::task` class directly. `concurrencpp::task` is an `std::function` like object, but with a few differences.\nLike `std::function`, the task object stores a callable that acts as the asynchronous operation.\nUnlike `std::function`, `task` is a move only type. On invocation, task objects receive no parameters and return `void`. Moreover, every task object can be invoked only once. After the first invocation, the task object becomes empty.\nInvoking an empty task object is equivalent to invoking an empty lambda (`[]{}`), and will not throw any exception.\nTask objects receive their callable as a forwarding reference (`type\u0026\u0026` where `type` is a template parameter), and not by copy (like `std::function`). Construction of the stored callable happens in-place. This allows task objects to contain callables that are move-only type (like `std::unique_ptr` and `concurrencpp::result`).\nTask objects try to use different methods to optimize the usage of the stored types, for example, task objects apply the short-buffer-optimization (sbo) for regular, small callables, and will inline calls to `std::coroutine_handle\u003cvoid\u003e` by calling them directly without virtual dispatch.    \n\n#### `task` API\n\n```cpp\n  class task {\n    /*\n        Creates an empty task object.\n    */\n    task() noexcept;\n        \n    /*\n        Creates a task object by moving the stored callable of rhs to *this.\n        If rhs is empty, then *this will also be empty after construction.\n        After this call, rhs is empty.\n    */\n    task(task\u0026\u0026 rhs) noexcept;\n\n    /*\n        Creates a task object by storing callable in *this.\n        \u003c\u003ctypename std::decay\u003ccallable_type\u003e::type\u003e\u003e will be in-place-\n        constructed inside *this by perfect forwarding callable.\n    */\n    template\u003cclass callable_type\u003e\n    task(callable_type\u0026\u0026 callable);\n\n    /*\n        Destroys stored callable, does nothing if empty.\n    */\n     ~task() noexcept;\n\t\n    /*\n        If *this is empty, does nothing.\n        Invokes stored callable, and immediately destroys it.\n        After this call, *this is empty.\n        May throw any exception that the invoked callable may throw.\n    */\n    void operator()();\n\n    /*\n        Moves the stored callable of rhs to *this.\n        If rhs is empty, then *this will also be empty after this call.    \n        If *this already contains a stored callable, operator = destroys it first.\n    */\n    task\u0026 operator=(task\u0026\u0026 rhs) noexcept;\n\n    /*\n        If *this is not empty, task::clear destroys the stored callable and empties *this.\n        If *this is empty, clear does nothing.\n    */\n    void clear() noexcept;\n\n    /*\n        Returns true if *this stores a callable. false otherwise.\n    */\n    explicit operator bool() const noexcept;\n\n    /*\n        Returns true if *this stores a callable,\n        and that stored callable has the same type as \u003c\u003ctypename std::decay\u003ccallable_type\u003e::type\u003e\u003e  \n    */\n    template\u003cclass callable_type\u003e\n    bool contains() const noexcept;\n\n};\n```\nWhen implementing user-defined executors, it is up to the implementation to store `task` objects (when `enqueue` is called), and execute them according to the executor inner-mechanism.\n\n#### Example: writing a user-defined executor:\n\nIn this example, we create an executor which logs actions like enqueuing tasks or executing them. We implement the `executor` interface, and we request the runtime to create and store an instance of it by calling `runtime::make_executor`. The rest of the application behaves exactly the same as if we were to use non user-defined executors.\n\n```cpp\n#include \"concurrencpp/concurrencpp.h\"\n\n#include \u003ciostream\u003e\n#include \u003cqueue\u003e\n#include \u003cthread\u003e\n#include \u003cmutex\u003e\n#include \u003ccondition_variable\u003e\n\nclass logging_executor : public concurrencpp::derivable_executor\u003clogging_executor\u003e {\n\nprivate:\n    mutable std::mutex _lock;\n    std::queue\u003cconcurrencpp::task\u003e _queue;\n    std::condition_variable _condition;\n    bool _shutdown_requested;\n    std::thread _thread;\n    const std::string _prefix;\n\n    void work_loop() {\n        while (true) {\n            std::unique_lock\u003cstd::mutex\u003e lock(_lock);\n            if (_shutdown_requested) {\n                return;\n            }\n\n            if (!_queue.empty()) {\n                auto task = std::move(_queue.front());\n                _queue.pop();\n                lock.unlock();\n                std::cout \u003c\u003c _prefix \u003c\u003c \" A task is being executed\" \u003c\u003c std::endl;\n                task();\n                continue;\n            }\n\n            _condition.wait(lock, [this] {\n                return !_queue.empty() || _shutdown_requested;\n            });\n        }\n    }\n\npublic:\n    logging_executor(std::string_view prefix) :\n        derivable_executor\u003clogging_executor\u003e(\"logging_executor\"),\n        _shutdown_requested(false),\n        _prefix(prefix) {\n        _thread = std::thread([this] {\n            work_loop();\n        });\n    }\n\n    void enqueue(concurrencpp::task task) override {\n        std::cout \u003c\u003c _prefix \u003c\u003c \" A task is being enqueued!\" \u003c\u003c std::endl;\n\n        std::unique_lock\u003cstd::mutex\u003e lock(_lock);\n        if (_shutdown_requested) {\n            throw concurrencpp::errors::runtime_shutdown(\"logging executor - executor was shutdown.\");\n        }\n\n        _queue.emplace(std::move(task));\n        _condition.notify_one();\n    }\n\n    void enqueue(std::span\u003cconcurrencpp::task\u003e tasks) override {\n        std::cout \u003c\u003c _prefix \u003c\u003c tasks.size() \u003c\u003c \" tasks are being enqueued!\" \u003c\u003c std::endl;\n\n        std::unique_lock\u003cstd::mutex\u003e lock(_lock);\n        if (_shutdown_requested) {\n            throw concurrencpp::errors::runtime_shutdown(\"logging executor - executor was shutdown.\");\n        }\n\n        for (auto\u0026 task : tasks) {\n            _queue.emplace(std::move(task));\n        }\n\n        _condition.notify_one();\n    }\n\n    int max_concurrency_level() const noexcept override {\n        return 1;\n    }\n\n    bool shutdown_requested() const noexcept override {\n        std::unique_lock\u003cstd::mutex\u003e lock(_lock);\n        return _shutdown_requested;\n    }\n\n    void shutdown() noexcept override {\n        std::cout \u003c\u003c _prefix \u003c\u003c \" shutdown requested\" \u003c\u003c std::endl;\n\n        std::unique_lock\u003cstd::mutex\u003e lock(_lock);\n        if (_shutdown_requested) return; //nothing to do.\n        _shutdown_requested = true;\n        lock.unlock();\n\n        _condition.notify_one();\n        _thread.join();\n    }\n};\n\nint main() {\n    concurrencpp::runtime runtime;\n    auto logging_ex = runtime.make_executor\u003clogging_executor\u003e(\"Session #1234\");\n\n    for (size_t i = 0; i \u003c 10; i++) {\n        logging_ex-\u003epost([] {\n            std::cout \u003c\u003c \"hello world\" \u003c\u003c std::endl;\n        });\n    }\n\n    std::getchar();\n    return 0;\n}\n```\n\n### Supported platforms and tools\n\n* **Operating systems:** Linux, macOS, Windows (Windows 10 and above)\n* **Compilers:** MSVC (Visual Studio 2019 version 16.8.2 and above), Clang 14+, Clang 11-13 with libc++, GCC 13+\n* **Tools:** CMake (3.16 and above)\n\n### Building, installing and testing\n\n##### Building the library on Windows (release mode)\n```cmake\n$ git clone https://github.com/David-Haim/concurrencpp.git\n$ cd concurrencpp\n$ cmake -S . -B build/lib\n$ cmake --build build/lib --config Release\n```\n##### Running the tests on Windows (debug + release mode)\n```cmake\n$ git clone https://github.com/David-Haim/concurrencpp.git\n$ cd concurrencpp\n$ cmake -S test -B build/test\n$ cmake --build build/test\n    \u003c# for release mode: cmake --build build/test --config Release #\u003e\n$ cd build/test\n$ ctest . -V -C Debug\n    \u003c# for release mode: ctest . -V -C Release #\u003e\n```\n##### Building the library on *nix platforms (release mode)\n```cmake\n$ git clone https://github.com/David-Haim/concurrencpp.git\n$ cd concurrencpp\n$ cmake -DCMAKE_BUILD_TYPE=Release -S . -B build/lib\n$ cmake --build build/lib\n    #optional, install the library: sudo cmake --install build/lib\n```\n##### Running the tests on *nix platforms \n\nWith clang and gcc, it is also possible to run the tests with TSAN (thread sanitizer) support.\n\n```cmake\n$ git clone https://github.com/David-Haim/concurrencpp.git\n$ cd concurrencpp\n$ cmake -S test -B build/test\n  #for release mode: cmake -DCMAKE_BUILD_TYPE=Release -S test -B build/test\n  #for TSAN mode: cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_THREAD_SANITIZER=Yes -S test -B build/test\n$ cmake --build build/test  \n$ cd build/test\n$ ctest . -V\n```\n##### Important note regarding Linux and libc++\nWhen compiling on Linux, the library tries to use `libstdc++` by default. If you intend to use `libc++` as your standard library implementation, `CMAKE_TOOLCHAIN_FILE` flag should be specified as below: \n\n```cmake\n$ cmake -DCMAKE_TOOLCHAIN_FILE=../cmake/libc++.cmake -DCMAKE_BUILD_TYPE=Release -S . -B build/lib\n```\n\n##### Installing concurrencpp with vcpkg or Conan\n\nAlternatively to building and installing the library manually, developers may get stable releases of concurrencpp via the [vcpkg](https://vcpkg.io/) and [Conan](https://conan.io/) package managers:\n\nvcpkg:\n```shell\n$ vcpkg install concurrencpp\n```\n\nConan: [concurrencpp on ConanCenter](https://conan.io/center/concurrencpp)\n\n##### Experimenting with the built-in sandbox\nconcurrencpp comes with a built-in sandbox program which developers can modify and experiment, without having to install or link the compiled library to a different code-base. In order to play with the sandbox, developers can modify `sandbox/main.cpp` and compile the application using the following commands:\n\n##### Building and running the sandbox on Windows:\n```cmake\n$ cmake -S sandbox -B build/sandbox\n$ cmake --build build/sandbox\n    \u003c# for release mode: cmake --build build/sandbox --config Release #\u003e\n$ ./build/sandbox \u003c# runs the sandbox\u003e\n```\n##### Building and running the sandbox on *nix platforms:\n```cmake\n$ cmake -S sandbox -B build/sandbox\n  #for release mode: cmake -DCMAKE_BUILD_TYPE=Release -S sandbox -B build/sandbox\n$ cmake --build build/sandbox  \n$ ./build/sandbox #runs the sandbox\n```","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavid-haim%2Fconcurrencpp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavid-haim%2Fconcurrencpp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavid-haim%2Fconcurrencpp/lists"}