Awesome
Quantum Library : A scalable C++ coroutine framework
Quantum is a full-featured and powerful C++ framework build on top of the Boost coroutine library. The framework allows users to dispatch units of work (a.k.a. tasks) as coroutines and execute them concurrently using the 'reactor' pattern.
Features
- NEW Added support for simpler V2 coroutine API which returns computed values directly.
- Header-only library and interface-based design.
- Full integration with Boost asymmetric coroutine library.
- Highly parallelized coroutine framework for CPU-bound workloads.
- Support for long-running or blocking IO tasks.
- Allows explicit and implicit cooperative yielding between coroutines.
- Task continuations and coroutine chaining for serializing work execution.
- Synchronous and asynchronous dispatching using futures and promises similar to STL.
- Support for streaming futures which allows faster processing of large data sets.
- Support for future references.
- Cascading execution output during task continuations (a.k.a. past futures).
- Task prioritization.
- Internal error handling and exception forwarding.
- Ability to write lock-free code by synchronizing coroutines on dedicated queues.
- Coroutine-friendly mutexes and condition variables for locking critical code paths or synchronizing access to external objects.
- Fast pre-allocated memory pools for internal objects and coroutines.
- Parallel
forEach
andmapReduce
functions. - Various stats API.
Sequencer
class allowing strict FIFO ordering of tasks based on sequence ids.
Sample code
Quantum is very simple and easy to use:
using namespace Bloomberg::quantum;
// Define a coroutine
int getDummyValue(CoroContextPtr<int> ctx)
{
int value;
... //do some work
ctx->yield(); //be nice and let other coroutines run (optional cooperation)
... //do more work and calculate 'value'
return ctx->set(value);
}
// Create a dispatcher
Dispatcher dispatcher;
// Dispatch a work item to do some work and return a value
int result = dispatcher.post(getDummyValue)->get();
Chaining tasks can also be straightforward. In this example we produce various types in a sequence.
using namespace Bloomberg::quantum;
// Create a dispatcher
Dispatcher dispatcher;
auto ctx = dispatcher.postFirst([](CoroContextPtr<int> ctx)->int {
return ctx->set(55); //Set the 1st value
})->then([](CoroContextPtr<double> ctx)->int {
// Get the first value and add something to it
return ctx->set(ctx->getPrev<int>() + 22.33); //Set the 2nd value
})->then([](CoroContextPtr<std::string> ctx)->int {
return ctx->set("Hello world!"); //Set the 3rd value
})->finally([](CoroContextPtr<std::list<int>> ctx)->int {
return ctx->set(std::list<int>{1,2,3}); //Set 4th value
})->end();
int i = ctx->getAt<int>(0); //This will throw 'FutureAlreadyRetrievedException'
//since future was already read in the 2nd coroutine
double d = ctx->getAt<double>(1); //returns 77.33
std::string s = ctx->getAt<std::string>(2); //returns "Hello world!";
std::list<int>& listRef = ctx->getRefAt<std::list<int>>(3); //get list reference
std::list<int>& listRef2 = ctx->getRef(); //get another list reference.
//The 'At' overload is optional for last chain future
std::list<int> listValue = ctx->get(); //get list value
Chaining with the new V2 api:
using namespace Bloomberg::quantum;
// Create a dispatcher
Dispatcher dispatcher;
auto ctx = dispatcher.postFirst([](VoidContextPtr ctx)->int {
return 55; //Set the 1st value
})->then([](VoidContextPtr ctx)->double {
// Get the first value and add something to it
return ctx->getPrev<int>() + 22.33; //Set the 2nd value
})->then([](VoidContextPtr ctx)->std::string {
return "Hello world!"; //Set the 3rd value
})->finally([](VoidContextPtr ctx)->std::list<int> {
return {1,2,3}; //Set 4th value
})->end();
Building and installing
Quantum is a header-only library and as such no targets need to be built. To install simply run:
> cmake -Bbuild <options> .
> cd build
> make install
CMake options
Various CMake options can be used to configure the output:
QUANTUM_BUILD_DOC
: Build Doxygen documentation. DefaultOFF
.QUANTUM_ENABLE_DOT
: Enable generation of DOT viewer files. DefaultOFF
.QUANTUM_VERBOSE_MAKEFILE
: Enable verbose cmake output. DefaultON
.QUANTUM_ENABLE_TESTS
: Builds thetests
target. DefaultOFF
.QUANTUM_BOOST_STATIC_LIBS
: Link with Boost static libraries. DefaultON
.QUANTUM_BOOST_USE_MULTITHREADED
: Use Boost multi-threaded libraries. DefaultON
.QUANTUM_USE_DEFAULT_ALLOCATOR
: Use default system supplied allocator instead of Quantum's. DefaultOFF
.QUANTUM_ALLOCATE_POOL_FROM_HEAP
: Pre-allocates object pools from heap instead of the application stack. DefaultOFF
.QUANTUM_BOOST_USE_SEGMENTED_STACKS
: Use Boost segmented stacks for coroutines. DefaultOFF
.QUANTUM_BOOST_USE_PROTECTED_STACKS
: Use Boost protected stacks for coroutines (slow!). DefaultOFF
.QUANTUM_BOOST_USE_FIXEDSIZE_STACKS
: Use Boost fixed size stacks for coroutines. DefaultOFF
.QUANTUM_INSTALL_ROOT
: Specify custom install path. Default is/usr/local/include
for Linux orc:/Program Files
for Windows.QUANTUM_PKGCONFIG_DIR
: Specify custom install path for thequantum.pc
file. Default is${QUANTUM_INSTALL_ROOT}/share/pkgconfig
. To specify a relative path fromQUANTUM_INSTALL_ROOT
, omit leading/
.QUANTUM_EXPORT_PKGCONFIG
: Generatequantum.pc
file. DefaultON
.QUANTUM_CMAKE_CONFIG_DIR
: Specify a different install directory for the project's config, target and version files. Default is${QUANTUM_INSTALL_ROOT}/share/cmake
.QUANTUM_EXPORT_CMAKE_CONFIG
: Generate CMake config, target and version files. DefaultON
.BOOST_ROOT
: Specify a different Boost install directory.GTEST_ROOT
: Specify a different GTest install directory.
Note: options must be preceded with -D
when passed as arguments to CMake.
Running tests
Run the following from the top directory:
> cmake -Bbuild -DQUANTUM_ENABLE_TESTS=ON <options> .
> cd build
> make quantum_test && ctest
Using
To use the library simply include <quantum/quantum.h>
in your application. Also, the following libraries must be included in the link:
boost_context
pthread
Quantum library is fully is compatible with C++11
, C++14
and C++17
language features. See compiler options below for more details.
Compiler options
The following compiler options can be set when building your application:
__QUANTUM_PRINT_DEBUG
: Prints debug and error information tostdout
andstderr
respectively.__QUANTUM_USE_DEFAULT_ALLOCATOR
: Disable pool allocation for internal objects (other than coroutine stacks) and use default system allocators instead.__QUANTUM_ALLOCATE_POOL_FROM_HEAP
: Pre-allocates object pools from heap instead of the application stack (default). This affects internal object allocations other than coroutines. Coroutine pools are always heap-allocated due to their size.__QUANTUM_BOOST_USE_SEGMENTED_STACKS
: Uses boost segmented stack for on-demand coroutine stack growth. Note that Boost.Context library must be built with propertysegmented-stacks=on
and applyingBOOST_USE_UCONTEXT
andBOOST_USE_SEGMENTED_STACKS
at b2/bjam command line.__QUANTUM_BOOST_USE_PROTECTED_STACKS
: Uses boost protected stack for runtime bound-checking. When using this option, coroutine creation (but not runtime efficiency) becomes more expensive.__QUANTUM_BOOST_USE_FIXEDSIZE_STACKS
: Uses boost fixed size stack. This defaults to system default allocator.
Application-wide settings
Various application-wide settings can be configured via ThreadTraits
, AllocatorTraits
and StackTraits
.
Documentation
Please see the wiki page for a detailed overview of this library, use-case scenarios and examples.
For class description visit the API reference page.