{"id":13830828,"url":"https://github.com/jeremyong/coop","last_synced_at":"2026-02-26T20:51:32.350Z","repository":{"id":137383609,"uuid":"342926176","full_name":"jeremyong/coop","owner":"jeremyong","description":"C++20 coroutines-based cooperative multitasking library","archived":false,"fork":false,"pushed_at":"2021-09-19T19:56:57.000Z","size":135,"stargazers_count":103,"open_issues_count":1,"forks_count":7,"subscribers_count":5,"default_branch":"main","last_synced_at":"2024-08-05T10:13:28.213Z","etag":null,"topics":["cooperative-multitasking","coroutines","cpp","cpp20","multithreading","thread-pool"],"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/jeremyong.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2021-02-27T18:16:10.000Z","updated_at":"2024-07-09T12:02:44.000Z","dependencies_parsed_at":null,"dependency_job_id":"5e4a381a-0420-4d0f-b631-a414f3eb4b80","html_url":"https://github.com/jeremyong/coop","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeremyong%2Fcoop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeremyong%2Fcoop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeremyong%2Fcoop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeremyong%2Fcoop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jeremyong","download_url":"https://codeload.github.com/jeremyong/coop/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225539553,"owners_count":17485351,"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":["cooperative-multitasking","coroutines","cpp","cpp20","multithreading","thread-pool"],"created_at":"2024-08-04T10:01:09.703Z","updated_at":"2026-02-26T20:51:32.307Z","avatar_url":"https://github.com/jeremyong.png","language":"C++","funding_links":[],"categories":["C++"],"sub_categories":[],"readme":"﻿# 🐔 Coop\r\n\r\nCoop is a C++20 coroutines-based library to support [*cooperative multitasking*](https://en.wikipedia.org/wiki/Cooperative_multitasking)\r\nin the context of a multithreaded application. The syntax will be familiar to users of `async` and `await` functionality in other\r\nprogramming languages. Users *do not* need to understand the C++20 coroutines API to use this library.\r\n\r\n## Features\r\n\r\n- Ships with a default affinity-aware two-priority threadsafe task scheduler.\r\n- The task scheduler is swappable with your own\r\n- Supports scheduling of user-defined code and OS completion events (e.g. events that signal after I/O completes)\r\n- Easy to use, efficient API, with a small and digestible code footprint (hundreds of lines of code, not thousands)\r\n\r\nTasks in Coop are *eager* as opposed to lazy, meaning that upon suspension, the coroutine is immediately dispatched for execution on\r\na worker with the appropriate affinity. While there are many benefits to structuring things lazily (see this excellent [talk](https://www.youtube.com/watch?v=1Wy5sq3s2rg)),\r\nCoop opts to do things the way it does because:\r\n\r\n- Coop was designed to interoperate with existing job/task graph systems\r\n- Coop was originally written within the context of a game engine, where exceptions were not used\r\n- For game engines, having a CPU-toplogy-aware dispatch mechanism is extremely important (consider the architecture of, say, the PS5)\r\n\r\nWhile game consoles don't (yet) support C++20 fully, the hope is that options like Coop will be there when the compiler support gets there as well.\r\n\r\n## Limitations\r\n\r\nIf your use case is too far abreast of Coop's original use case (as above), you may need to do more modification to get Coop to behave the way you want.\r\nThe limitations to consider below are:\r\n\r\n- Requires a recent C++20 compiler and code that uses Coop headers must also use C++20\r\n- The \"event_t\" wrapper around Win32 events doesn't have equivalent functionality on other platforms yet (it's provided as a reference for how you might handle your own overlapped IO)\r\n- The Clang implementation of the coroutines API at the moment doesn't work with the GCC stdlib++, so use libc++ instead\r\n- Clang on Windows does not yet support the MSVC coroutines runtime due to ABI differences\r\n- Coop ignores the problem of unhandled exceptions within scheduled tasks\r\n\r\nIf the above limitations make Coop unsuitable for you, consider the following libraries:\r\n\r\n- [CppCoro](https://github.com/lewissbaker/cppcoro) - A coroutine library for C++\r\n- [Conduit](https://github.com/loopperfect/conduit) - Lazy High Performance Streams using Coroutine TS\r\n- [folly::coro](https://github.com/facebook/folly/tree/master/folly/experimental/coro) - a developer-friendly asynchronous C++ framework based on Coroutines TS\r\n\r\n## Building and Running the Tests\r\n\r\nWhen configured as a standalone project, the built-in scheduler and tests are enabled by default. To configure and build the project\r\nfrom the command line:\r\n\r\n```bash\r\nmkdir build\r\ncd build\r\ncmake .. # Supply your own generator if you don't want the default generator\r\ncmake --build .\r\n./test/coop_test\r\n```\r\n\r\n## Integration Guide\r\n\r\nIf you don't intend on using the built in scheduler, simply copy the contents of the `include` folder somewhere in your include path.\r\n\r\nOtherwise, the recommended integration is done via cmake. For the header only portion, link against the `coop::coop_core` target.\r\n\r\nIf you'd like both headers and the scheduler implementation, link against `coop::coop`.\r\n\r\nDrop this quick cmake snippet somewhere in your `CMakeLists.txt` file to make both of these targets available.\r\n\r\n```cmake\r\ninclude(FetchContent)\r\n\r\nFetchContent_Declare(\r\n    coop\r\n    GIT_REPOSITORY https://github.com/jeremyong/coop.git\r\n    GIT_TAG master\r\n    GIT_SHALLOW ON\r\n)\r\nFetchContent_MakeAvailable(coop)\r\n```\r\n\r\n## Usage\r\n\r\nTo write a coroutine, you'll use the `task_t` template type.\r\n\r\n\r\n```c++\r\ncoop::task_t\u003c\u003e simple_coroutine()\r\n{\r\n    co_await coop::suspend();\r\n\r\n    // Fake some work with a timer\r\n    std::this_thread::sleep_for(std::chrono::milliseconds{50});\r\n}\r\n```\r\n\r\nThe first line with the `coop::suspend` function will suspend the execution of `simple_coroutine` and the next line will continue on a different thread.\r\n\r\nTo use this coroutine from another coroutine, we can do something like the following:\r\n\r\n```c++\r\ncoop::task_t\u003c\u003e another_coroutine()\r\n{\r\n    // This will cause `simple_coroutine` to be scheduled on a thread different to this one\r\n    auto task = simple_coroutine();\r\n\r\n    // Do other useful work\r\n\r\n    // Await the task when we need it to finish\r\n    co_await task;\r\n}\r\n```\r\n\r\nTasks can hold values to be awaited on.\r\n\r\n```c++\r\ncoop::task_t\u003cint\u003e coroutine_with_data()\r\n{\r\n    co_await coop::suspend();\r\n\r\n    // Do some work\r\n    int result = some_expensive_simulation();\r\n\r\n    co_return result;\r\n}\r\n```\r\n\r\nWhen the task above is awaited via the `co_await` operator, what results is the int returned via `co_return`.\r\nOf course, passing other types is possible by changing the first template parameter of `task_t`.\r\n\r\nTasks let you do multiple async operations simultaneously, for example:\r\n\r\n```c++\r\ncoop::task_t\u003c\u003e my_task(int ms)\r\n{\r\n    co_await coop::suspend();\r\n\r\n    // Fake some work with a timer\r\n    std::this_thread::sleep_for(std::chrono::milliseconds{ms});\r\n}\r\n\r\ncoop::task_t\u003c\u003e big_coroutine()\r\n{\r\n    auto t1 = my_task(50);\r\n    auto t2 = my_task(40);\r\n    auto t3 = my_task(80);\r\n\r\n    // 3 invocations of `my_task` are now potentially running concurrently on different threads\r\n\r\n    do_something_useful();\r\n\r\n    // Suspend until t2 is done\r\n    co_await t2;\r\n\r\n    // Right now, t1 and t3 are *potentially* still running\r\n\r\n    do_something_else();\r\n\r\n    // When awaiting a task, this coroutine will not suspend if the task\r\n    // is already ready. Otherwise, this coroutine suspends to be continued\r\n    // by the thread that completes the awaited task.\r\n    co_await t1;\r\n    co_await t3;\r\n\r\n    // Now, all three tasks are complete\r\n}\r\n```\r\n\r\nOne thing to keep in mind is that after awaiting a task, the thread you resume on is *not* necessarily the same thread\r\nyou were on originally.\r\n\r\nWhat if you want to await a task from `main` or some other execution context that isn't a coroutine? For this, you can\r\nmake a joinable task and `join` it.\r\n\r\n```c++\r\ncoop::task_t\u003cvoid, true\u003e joinable_coroutine()\r\n{\r\n    co_await coop::suspend();\r\n\r\n    // Fake some work with a timer\r\n    std::this_thread::sleep_for(std::chrono::milliseconds{50});\r\n}\r\n\r\nint main(int argc, char** argv)\r\n{\r\n    auto task = joinable_coroutine();\r\n    // The timer is now running on a different thread than the main thread\r\n\r\n    // Pause execution until joinable_coroutine is finished on whichever thread it was scheduled on\r\n    task.join();\r\n\r\n    return 0;\r\n}\r\n```\r\n\r\nNote that currently, there is some overhead associated with spawning a joinable task because it creates new event objects instead of reusing event handles from a pool.\r\n\r\nThe `coop::suspend` function takes additional parameters that can set the CPU affinity mask, priority (only 0 and 1 are supported at the moment,\r\nwith 1 being the higher priority), and file/line information for debugging purposes.\r\n\r\nIn addition to awaiting tasks, you can also await the `event_t` object. While this currently only supports Windows, this lets a coroutine\r\nsuspend execution until an event handle is signaled - a powerful pattern for doing async I/O.\r\n\r\n```c++\r\ncoop::task_t\u003c\u003e wait_for_event()\r\n{\r\n    // Suppose file_reading_code produces a Win32 HANDLE which will get signaled whenever the file\r\n    // read is ready\r\n    coop::event_t event{file_reading_code()};\r\n\r\n    // Do something else while the file is reading\r\n\r\n    // Suspend until the event gets signaled\r\n    co_await event;\r\n}\r\n```\r\n\r\nIn the future, support may be added for epoll and kqueue abstractions.\r\n\r\n## Convenience macro `COOP_SUSPEND#`\r\n\r\nThe full function signature of the `suspend` function is the following:\r\n\r\n```c++\r\ntemplate \u003cScheduler S = scheduler_t\u003e\r\ninline auto suspend(S\u0026 scheduler                             = S::instance(),\r\n                    uint64_t cpu_mask                        = 0,\r\n                    uint32_t priority                        = 0,\r\n                    source_location_t const\u0026 source_location = {}) noexcept\r\n```\r\n\r\nand you must await the returned result. Instead, you can use the family of macros and simply write\r\n\r\n```\r\nCOOP_SUSPEND();\r\n```\r\n\r\nif you are comfortable with the default behavior. This macro will supply `__FILE__` and `__LINE__` information\r\nto the `source_location` paramter to get additional tracking. Other macros with numerical suffixes to `COOP_SUSPEND` are\r\nalso provided to allow you to override a subset of parameters as needed.\r\n\r\n## (Optional) Use your own scheduler\r\n\r\nCoop is designed to be a pretty thin abstraction layer to make writing async code more convenient. If you already have a robust\r\nscheduler and thread pool, you don't have to use the one provided here. The `coop::suspend` function is templated and accepts\r\nan optional first parameter to a class that implements the `Scheduler` concept. To qualify as a `Scheduler`, a class only needs\r\nto implement the following function signature:\r\n\r\n```c++\r\n    void schedule(std::coroutine_handle\u003c\u003e coroutine,\r\n                  uint64_t cpu_affinity             = 0,\r\n                  uint32_t priority                 = 0,\r\n                  source_location_t source_location = {});\r\n```\r\n\r\nThen, at the opportune time on a thread of your choosing, simply call `coroutine.resume()`. Remember that when implementing your\r\nown scheduler, you are responsible for thread safety and ensuring that the \"usual\" bugs (like missed notifications) are ironed out.\r\nYou can ignore the cpu affinity and priority flags if you don't need this functionality (i.e. if you aren't targeting a NUMA).\r\n\r\n## Hack away\r\n\r\nThe source code of Coop is pretty small all things considered, with the core of its functionality contained in only a few hundred\r\nlines of commented code. Feel free to take it and adapt it for your use case. This was the route taken as opposed to making every\r\ndesign aspect customizable (which would have made the interface far more complicated).\r\n\r\n## Additional Resources\r\n\r\nTo learn more about coroutines in C++20, please do visit this [awesome compendium](https://gist.github.com/MattPD/9b55db49537a90545a90447392ad3aeb)\r\nof resources compiled by @MattPD.\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjeremyong%2Fcoop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjeremyong%2Fcoop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjeremyong%2Fcoop/lists"}