A lock-free, wait-free, event-driven, cross-platform, thread pool-backed asynchronous execution library in C++.
Actually, cyan is a collection of smaller libraries that you can use separately as well.
common
contains generic utilities.event
contains C++11 abstractions over event libraries (currently only specialized for libev, but hopefully easy enough to extend to libevent).lockfree
contains my experiment with Michael and Scott's Queue, and an implementation of a lock-freestack
based on Anthony Williams's book, Concurrency in C++.dispatch
combinesevent
andlockfree
with threads and implements asynchronous dispatch.
Back in 2014, I worked for a telecom company where I had proposed an application architecture based solely on threads running event loops and responding to events, whether they be network events or messages from other threads. It was implemented using fixed-size SPSC lock-free queues. That architecture made it to production and handled over 60K TPS HTTP transactions on a single ATCA chassis blade.
Library components required to build such applications in C++ are few, so I hacked this project together in my free time.
API documentation is shoddy, or absent at best (I'm still working on that), but hopefully the examples will help.
Here are the results of running the factorial_benchmark
found in the examples
on my Mac Book Air.
% ./examples/factorial_benchmark
--- test 1: using std::async ---
Execution time: 15736ms
--- test 2: using cyan::dispatch::async_awaitable ---
Execution time: 12352ms
--- test 3: using cyan::dispatch::async ---
Execution time: 160ms
--- test 4: OpenMP execution ---
Execution time: 1743ms
--- test 5: OpenMP with dynamic scheduling ---
Execution time: 1806ms% sysctl -n machdep.cpu.brand_string
Intel(R) Core(TM) i7-4650U CPU @ 1.70GHz% sysctl -n machdep.cpu.core_count
2% clang -v
Apple clang version 11.0.3 (clang-1103.0.32.62)
Target: x86_64-apple-darwin19.5.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
Here are results for running boost_asio_benchmark
using boost::asio::io_context
running on separate thread vs a cyan::handler_thread
.
% ./examples/boost_asio_benchmark
---- test 1 using handler_thread ----
Execution time: 9108ms
Call count: 30000000
---- test 2 using boost::asio ----
Execution time: 16059ms
Call count: 30000000
% mkdir -p build
% cmake ..
% make
Although the libraries should compile on other platforms, they may require tweaks to CMakeLists.txt
because I have not tested on Linux and Windows yet.
Full examples are available in the source tree.
cyan::dispatch::async([] {
// your code here
});
cyan::dispatch::serial_token token;
cyan::dispatch::async(token, [] {
// First task
});
cyan::dispatch::async(token, [] {
// Task executed only after first task finishes
});
auto future = cyan::dispatch::async_awaitable([] {
// perform task
return result;
});
auto result = future.get();
class active_object : public cyan::dispatch::handler {
public:
active_object() : thread_{ *this } {
}
~active_object() {
thread_.stop();
}
void process_on_background_thread() {
// ...
thread_.send(message);
}
void execute_on_background_thread() {
thread_.post([] {
// ...
});
}
std::future<void> wait_for_result() {
return thread_.post_awaitable([] {
// ...
});
}
protected:
void on_initialize() override {
// Background thread has started
}
void on_message(std::any&& message) override {
// Callback on `handler_thread::send() or handler_thread::send_delayed()`
}
void on_finalize() override {
// Handler thread is exiting
}
void on_error(std::exception& e) noexcept override {
// Exception on background thread
}
private:
cyan::dispatch::handler_thread thread_;
};
cyan::event::timer timer{ cyan::this_thread::get_event_loop() };
timer.set_timeout(500ms);
timer.set_callback([] {
// 500ms expired
});
timer.start();
// do some work
timer.reset(); // reset timer on activity
cyan::this_thread::get_event_loop()->start();
cyan::event::timer_wheel timer_wheel{ cyan::this_thread::get_event_loop() };
timer_wheel.start();
auto id = timer_wheel.post([] { /** ... */ }, 500ms);
// ...
timer_wheel.cancel(id); // cancels if not already fired
cyan::event::async async{ cyan::event::get_main_loop() };
async.set_callback([] {
std::cout << "i'm awake, i'm awake!!" << std::endl;
});
async.start();
std::thread thread{ [&] {
std::this_thread::sleep_for(1s);
async.send();
}};
thread.detach();
cyan::event::get_main_loop()->start();
cyan::event::idle idle{ cyan::event::get_main_loop() };
idle.set_callback([&idle] {
std::cout << "i'm idle" << std::endl;
// Don't want it firing continually.
idle.stop();
});
idle.start();
// Starts and blocks on the event loop.
cyan::event::get_main_loop()->start();
cyan::event::signal signal{ cyan::this_thread::get_event_loop() };
signal.set_number(SIGINT);
signal.set_callback([] {
std::cout << "signal SIGINT received. Shutting down loop." << std::endl;
// Every thread gets its own event loop. This
// call returns the main event loop itself.
cyan::this_thread::get_event_loop()->stop();
});
signal.start();
cyan::event::get_main_loop()->start();
- Unit tests
- Documentation