### Install concurrencpp via vcpkg Source: https://github.com/david-haim/concurrencpp/blob/master/README.md Standard command to install the library using the vcpkg package manager. ```shell $ vcpkg install concurrencpp ``` -------------------------------- ### Install concurrencpp using CMake Source: https://github.com/david-haim/concurrencpp/blob/master/CMakeLists.txt This CMake script installs the concurrencpp library, its headers, and CMake configuration files. It defines installation directories for runtime and development components. ```cmake include(CMakePackageConfigHelpers) include(GNUInstallDirs) set(concurrencpp_directory "concurrencpp-${PROJECT_VERSION}") set(concurrencpp_include_directory "${CMAKE_INSTALL_INCLUDEDIR}/${concurrencpp_directory}") install( TARGETS concurrencpp EXPORT concurrencppTargets ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT concurrencpp_Development INCLUDES DESTINATION "${concurrencpp_include_directory}" LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT concurrencpp_Runtime NAMELINK_COMPONENT concurrencpp_Development RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT concurrencpp_Runtime) set(concurrencpp_install_cmakedir "${CMAKE_INSTALL_LIBDIR}/cmake/${concurrencpp_directory}") write_basic_package_version_file( concurrencppConfigVersion.cmake VERSION ${PROJECT_VERSION} COMPATIBILITY SameMinorVersion ARCH_INDEPENDENT) install( EXPORT concurrencppTargets NAMESPACE concurrencpp:: DESTINATION "${concurrencpp_install_cmakedir}" COMPONENT concurrencpp_Development) install( FILES "${PROJECT_SOURCE_DIR}/cmake/concurrencppConfig.cmake" "${PROJECT_BINARY_DIR}/concurrencppConfigVersion.cmake" DESTINATION "${concurrencpp_install_cmakedir}" COMPONENT concurrencpp_Development) install( DIRECTORY "${PROJECT_SOURCE_DIR}/include/" DESTINATION "${concurrencpp_include_directory}" COMPONENT concurrencpp_Development) ``` -------------------------------- ### Hello World with concurrencpp Source: https://github.com/david-haim/concurrencpp/blob/master/README.md Demonstrates a basic concurrencpp program. It initializes a runtime, submits a task to the thread executor, and waits for its completion using `get()`. ```cpp #include "concurrencpp/concurrencpp.h" #include int main() { concurrencpp::runtime runtime; auto result = runtime.thread_executor()->submit([] { std::cout << "hello world" << std::endl; }); result.get(); return 0; } ``` -------------------------------- ### Task with Delay Object Example Source: https://github.com/david-haim/concurrencpp/blob/master/README.md This example demonstrates spawning a task that repeatedly delays itself using a delay object. It requires the concurrencpp library and standard C++ headers. ```cpp #include "concurrencpp/concurrencpp.h" #include using namespace std::chrono_literals; concurrencpp::null_result delayed_task( std::shared_ptr tq, std::shared_ptr ex) { size_t counter = 1; while(true) { std::cout << "task was invoked " << counter << " times." << std::endl; counter++; co_await tq->make_delay_object(1500ms, ex); } } int main() { concurrencpp::runtime runtime; delayed_task(runtime.timer_queue(), runtime.thread_pool_executor()); std::this_thread::sleep_for(10s); return 0; } ``` -------------------------------- ### Async Lock Try-Lock Example Source: https://context7.com/david-haim/concurrencpp/llms.txt Demonstrates the usage of `try_lock` with an `async_lock`. It attempts to acquire the lock immediately and proceeds only if successful, releasing it afterward. ```cpp // try_lock example auto try_result = data_lock.try_lock(); if (co_await try_result) { std::cout << "Lock acquired via try_lock" ; data_lock.unlock(); } ``` -------------------------------- ### Delay Object Example Source: https://github.com/david-haim/concurrencpp/blob/master/README.md Example demonstrating the usage of a delay object within a coroutine to introduce asynchronous delays. ```APIDOC ## Delay Object Example ### Description This example demonstrates how to use a delay object within a coroutine to create asynchronous delays. The task repeatedly delays itself using `co_await` on a delay object. ### Code Example ```cpp #include "concurrencpp/concurrencpp.h" #include using namespace std::chrono_literals; concurrencpp::null_result delayed_task( std::shared_ptr tq, std::shared_ptr ex) { size_t counter = 1; while(true) { std::cout << "task was invoked " << counter << " times." << std::endl; counter++; co_await tq->make_delay_object(1500ms, ex); } } int main() { concurrencpp::runtime runtime; delayed_task(runtime.timer_queue(), runtime.thread_pool_executor()); std::this_thread::sleep_for(10s); return 0; } ``` ``` -------------------------------- ### result_promise Usage Example Source: https://github.com/david-haim/concurrencpp/blob/master/README.md Example demonstrating how to use result_promise to pass data from a third-party thread to a concurrencpp result object. ```APIDOC ```cpp #include "concurrencpp/concurrencpp.h" #include int main() { concurrencpp::result_promise promise; auto result = promise.get_result(); std::thread my_3_party_executor([promise = std::move(promise)] () mutable { std::this_thread::sleep_for(std::chrono::seconds(1)); promise.set_result("hello world"); }); auto asynchronous_string = result.get(); std::cout << "result promise returned string: " << asynchronous_string << std::endl; my_3_party_executor.join(); } ``` ``` -------------------------------- ### Shared Result Example Source: https://github.com/david-haim/concurrencpp/blob/master/README.md Demonstrates the usage of `shared_result` by converting a regular result to a shared one and having multiple tasks concurrently await its value. ```APIDOC ## `shared_result` Example ### Description This example showcases how to convert a `result` object into a `shared_result` object. It then demonstrates acquiring a reference to an asynchronous integer result, which is then accessed by multiple tasks spawned using a `thread_executor`. ### Code Example ```cpp #include "concurrencpp/concurrencpp.h" #include #include concurrencpp::result consume_shared_result(concurrencpp::shared_result shared_result, std::shared_ptr resume_executor) { std::cout << "Awaiting shared_result to have a value" << std::endl; const auto& async_value = co_await shared_result; concurrencpp::resume_on(resume_executor); std::cout << "In thread id " << std::this_thread::get_id() << ", got: " << async_value << ", memory address: " << &async_value << std::endl; } int main() { concurrencpp::runtime runtime; auto result = runtime.background_executor()->submit([] { std::this_thread::sleep_for(std::chrono::seconds(1)); return 100; }); concurrencpp::shared_result shared_result(std::move(result)); concurrencpp::result results[8]; for (size_t i = 0; i < 8; i++) { results[i] = consume_shared_result(shared_result, runtime.thread_pool_executor()); } std::cout << "Main thread waiting for all consumers to finish" << std::endl; auto tpe = runtime.thread_pool_executor(); auto all_consumed = concurrencpp::when_all(tpe, std::begin(results), std::end(results)).run(); all_consumed.get(); std::cout << "All consumers are done, exiting" << std::endl; return 0; } ``` ``` -------------------------------- ### Asynchronous Result Handling with co_await Source: https://context7.com/david-haim/concurrencpp/llms.txt Demonstrates non-blocking consumption of asynchronous results using co_await and blocking retrieval with get(). ```cpp #include "concurrencpp/concurrencpp.h" #include #include using namespace concurrencpp; // Coroutine that processes data asynchronously result process_data(std::shared_ptr executor, int input) { // Submit work to thread pool auto task_result = executor->submit([input]() -> int { // Simulate processing return input * 2; }); // co_await suspends until result is ready (non-blocking) int processed = co_await task_result; co_return processed + 10; } // Chain multiple async operations result async_pipeline(std::shared_ptr executor) { auto r1 = executor->submit([] { return 5; }); auto r2 = executor->submit([] { return 10; }); int val1 = co_await r1; int val2 = co_await r2; co_return "Result: " + std::to_string(val1 + val2); } int main() { concurrencpp::runtime runtime; auto executor = runtime.thread_pool_executor(); // Use coroutine auto result = process_data(executor, 5); std::cout << "Processed: " << result.get() << std::endl; // Chain operations auto pipeline_result = async_pipeline(executor); std::cout << pipeline_result.get() << std::endl; // Check result status before blocking auto async_result = executor->submit([] { return 100; }); async_result.wait(); // Block until ready if (async_result.status() == result_status::value) { std::cout << "Value ready: " << async_result.get() << std::endl; } return 0; } // Output: // Processed: 20 // Result: 15 // Value ready: 100 ``` -------------------------------- ### Async Lock Main Function Source: https://context7.com/david-haim/concurrencpp/llms.txt Sets up a `concurrencpp::runtime` and an executor, then launches multiple writer coroutines concurrently. It waits for all writers to complete and then calls a reader coroutine to display the final state of shared data. Includes a `try_lock` example. ```cpp int main() { concurrencpp::runtime runtime; auto executor = runtime.thread_pool_executor(); // Launch concurrent writers result writers[5]; for (int i = 0; i < 5; i++) { writers[i] = writer({}, executor, i * 10); } // Wait for all writers for (auto& w : writers) { w.get(); } // Read final state reader({}, executor).get(); // try_lock example auto try_result = data_lock.try_lock(); if (co_await try_result) { std::cout << "Lock acquired via try_lock" ; data_lock.unlock(); } return 0; } ``` -------------------------------- ### Async Queue Main Function Source: https://context7.com/david-haim/concurrencpp/llms.txt Demonstrates the usage of the `async_queue` class. It creates a queue, pushes three items into it, then pops and prints three items. This showcases the producer-consumer pattern with asynchronous operations. ```cpp int main() { concurrencpp::runtime runtime; auto executor = runtime.thread_pool_executor(); async_queue queue; // Producer for (int i = 1; i <= 3; i++) { queue.push(executor, i * 10).run().get(); } // Consumer for (int i = 0; i < 3; i++) { auto val = queue.pop(executor).run().get(); if (val) { std::cout << "Popped: " << *val << std::endl; } } return 0; } ``` -------------------------------- ### Initialize Concurrencpp Runtime Source: https://context7.com/david-haim/concurrencpp/llms.txt Create a runtime instance to manage executors and access library version information. ```cpp #include "concurrencpp/concurrencpp.h" #include int main() { // Create runtime with default options concurrencpp::runtime runtime; // Access built-in executors auto thread_pool = runtime.thread_pool_executor(); // For CPU-bound tasks auto background = runtime.background_executor(); // For blocking I/O tasks auto thread_ex = runtime.thread_executor(); // New thread per task auto inline_ex = runtime.inline_executor(); // Execute inline auto timer_q = runtime.timer_queue(); // For timers // Create custom executors auto worker = runtime.make_worker_thread_executor(); // Single dedicated thread auto manual = runtime.make_manual_executor(); // Manual task execution // Get library version auto [major, minor, patch] = concurrencpp::runtime::version(); std::cout << "concurrencpp version: " << major << "." << minor << "." << patch << std::endl; return 0; } // Output: concurrencpp version: 0.1.7 ``` -------------------------------- ### Build and run sandbox on *nix Source: https://github.com/david-haim/concurrencpp/blob/master/README.md Commands to compile and execute the sandbox application on *nix platforms. ```cmake $ cmake -S sandbox -B build/sandbox #for release mode: cmake -DCMAKE_BUILD_TYPE=Release -S sandbox -B build/sandbox $ cmake --build build/sandbox $ ./build/sandbox #runs the sandbox ``` -------------------------------- ### async_lock Constructor Source: https://github.com/david-haim/concurrencpp/blob/master/README.md Constructs an async_lock object. No special setup is required. ```cpp async_lock() noexcept; ``` -------------------------------- ### Get Pointer to Wrapped async_lock Source: https://github.com/david-haim/concurrencpp/blob/master/README.md Returns a pointer to the wrapped async_lock, or a null pointer if there is no wrapped async_lock. ```cpp async_lock* mutex() const noexcept; ``` -------------------------------- ### Build and test Concurrencpp with CMake Source: https://github.com/david-haim/concurrencpp/blob/master/README.md Commands for cloning, building, and testing the library on Windows and Unix-like systems. ```cmake $ git clone https://github.com/David-Haim/concurrencpp.git $ cd concurrencpp $ cmake -S . -B build/lib $ cmake --build build/lib --config Release ``` ```cmake $ git clone https://github.com/David-Haim/concurrencpp.git $ cd concurrencpp $ cmake -S test -B build/test $ cmake --build build/test <# for release mode: cmake --build build/test --config Release #> $ cd build/test $ ctest . -V -C Debug <# for release mode: ctest . -V -C Release #> ``` ```cmake $ git clone https://github.com/David-Haim/concurrencpp.git $ cd concurrencpp $ cmake -DCMAKE_BUILD_TYPE=Release -S . -B build/lib $ cmake --build build/lib #optional, install the library: sudo cmake --install build/lib ``` -------------------------------- ### Build and test concurrencpp on *nix Source: https://github.com/david-haim/concurrencpp/blob/master/README.md Commands to clone the repository and build the test suite, including optional configurations for release and TSAN modes. ```cmake $ git clone https://github.com/David-Haim/concurrencpp.git $ cd concurrencpp $ cmake -S test -B build/test #for release mode: cmake -DCMAKE_BUILD_TYPE=Release -S test -B build/test #for TSAN mode: cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_THREAD_SANITIZER=Yes -S test -B build/test $ cmake --build build/test $ cd build/test $ ctest . -V ``` -------------------------------- ### Get Manual Executor Size Source: https://github.com/david-haim/concurrencpp/blob/master/README.md Returns the number of enqueued tasks. This value is a hint and can change quickly. The method is thread-safe. ```cpp size_t size() const noexcept; ``` -------------------------------- ### Build and run sandbox on Windows Source: https://github.com/david-haim/concurrencpp/blob/master/README.md Commands to compile and execute the sandbox application on Windows platforms. ```cmake $ cmake -S sandbox -B build/sandbox $ cmake --build build/sandbox <# for release mode: cmake --build build/sandbox --config Release #> $ ./build/sandbox <# runs the sandbox> ``` -------------------------------- ### Range Generator with Validation Source: https://context7.com/david-haim/concurrencpp/llms.txt A generator that yields integers within a specified range. It includes validation to ensure the start is not greater than the end. ```cpp // Generator with exception handling generator range_with_validation(int start, int end) { if (start > end) { throw std::invalid_argument("start must be <= end"); } for (int i = start; i <= end; i++) { co_yield i; } } ``` -------------------------------- ### Create ready result with make_ready_result Source: https://github.com/david-haim/concurrencpp/blob/master/README.md Creates a ready result object from given arguments, allowing immediate resumption when awaited. ```cpp /* Creates a ready result object by building <> from arguments&&... in-place. Might throw any exception that the constructor of type(std::forward(arguments)...) throws. Might throw std::bad_alloc exception if fails to allocate memory. */ template result make_ready_result(argument_types&& ... arguments); /* An overload for void type. Might throw std::bad_alloc exception if fails to allocate memory. */ result make_ready_result(); ``` -------------------------------- ### Infinite Counter Generator Source: https://context7.com/david-haim/concurrencpp/llms.txt An infinite generator that continuously yields incrementing integers starting from 0. Use with caution to avoid infinite loops. ```cpp // Infinite generator generator infinite_counter() { int i = 0; while (true) { co_yield i++; } } ``` -------------------------------- ### Consuming Generators in C++ Source: https://context7.com/david-haim/concurrencpp/llms.txt Demonstrates how to consume values from different types of generators using range-based for loops and manual iteration with a break condition. ```cpp int main() { // Consume generator in range-for loop std::cout << "Fibonacci: "; for (int n : fibonacci_generator(10)) { std::cout << n << " "; } std::cout << std::endl; // Range generator std::cout << "Range: "; for (int n : range_with_validation(1, 5)) { std::cout << n << " "; } std::cout << std::endl; // Take from infinite generator std::cout << "First 5 from infinite: "; int count = 0; for (int n : infinite_counter()) { std::cout << n << " "; if (++count >= 5) break; } std::cout << std::endl; return 0; } ``` -------------------------------- ### CMakeLists.txt for Concurrencpp Project Source: https://github.com/david-haim/concurrencpp/blob/master/sandbox/CMakeLists.txt This CMakeLists.txt file configures a C++20 project, fetches the concurrencpp library, and links it to the executable. Ensure coroutineOptions.cmake is in the correct relative path. ```cmake cmake_minimum_required(VERSION 3.16) project(sandbox LANGUAGES CXX) include(FetchContent) FetchContent_Declare(concurrencpp SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/..") FetchContent_MakeAvailable(concurrencpp) include(../cmake/coroutineOptions.cmake) add_executable(sandbox main.cpp) target_compile_features(sandbox PRIVATE cxx_std_20) target_link_libraries(sandbox PRIVATE concurrencpp::concurrencpp) target_coroutine_options(sandbox) ``` -------------------------------- ### Configure CMake for Concurrencpp Source: https://github.com/david-haim/concurrencpp/blob/master/example/3_async_file_processing/CMakeLists.txt Sets up the project, fetches the concurrencpp library, and links it to an executable requiring C++20 coroutine support. ```cmake cmake_minimum_required(VERSION 3.16) project(3_async_file_processing LANGUAGES CXX) include(FetchContent) FetchContent_Declare(concurrencpp SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") FetchContent_MakeAvailable(concurrencpp) include(../../cmake/coroutineOptions.cmake) add_executable(3_async_file_processing source/main.cpp) target_compile_features(3_async_file_processing PRIVATE cxx_std_20) target_link_libraries(3_async_file_processing PRIVATE concurrencpp::concurrencpp) target_coroutine_options(3_async_file_processing) ``` -------------------------------- ### result_promise Example: Thread Communication Source: https://github.com/david-haim/concurrencpp/blob/master/README.md Demonstrates using result_promise to pass a string from a separate thread to the main thread. The main thread retrieves the result, blocking until it's available. ```cpp #include "concurrencpp/concurrencpp.h" #include int main() { concurrencpp::result_promise promise; auto result = promise.get_result(); std::thread my_3_party_executor([promise = std::move(promise)] () mutable { std::this_thread::sleep_for(std::chrono::seconds(1)); //Imitate real work promise.set_result("hello world"); }); auto asynchronous_string = result.get(); std::cout << "result promise returned string: " << asynchronous_string << std::endl; my_3_party_executor.join(); } ``` -------------------------------- ### shared_result Result Retrieval Source: https://github.com/david-haim/concurrencpp/blob/master/README.md Methods for retrieving the actual result or exception from a shared result. `get()` blocks until the result is ready and returns a reference to the value or rethrows the exception. `operator co_await` provides coroutine-based asynchronous retrieval. ```cpp /* Blocks the current thread of execution until this shared-result is ready, when status() != result_status::idle. If the result is a valid value, a reference to it is returned, otherwise, get rethrows the asynchronous exception. Throws errors::empty_result if *this is empty. Might throw std::system_error if one of the underlying synchronization primitives throws. */ std::add_lvalue_reference_t get(); /* Returns an awaitable used to await this shared-result. If the shared-result is already ready - the current coroutine resumes immediately in the calling thread of execution. If the shared-result is not ready yet, the current coroutine is suspended and resumed when the asynchronous result is ready, by the thread which had set the asynchronous value or exception. In either way, after resuming, if the result is a valid value, a reference to it is returned. Otherwise, operator co_await rethrows the asynchronous exception. Throws errors::empty_result if *this is empty. */ auto operator co_await(); /* Returns an awaitable used to resolve this shared-result. After co_await expression finishes, *this is returned in a non-empty form, in a ready state. Throws errors::empty_result if *this is empty. */ auto resolve(); }; ``` -------------------------------- ### Manual Executor Task Execution Source: https://context7.com/david-haim/concurrencpp/llms.txt Demonstrates manual executor usage for enqueuing, executing tasks one by one or in batches, and checking task status. Requires concurrencpp library. ```cpp #include "concurrencpp/concurrencpp.h" #include int main() { concurrencpp::runtime runtime; auto manual = runtime.make_manual_executor(); // Enqueue tasks manual->post([] { std::cout << "Task 1" << std::endl; }); manual->post([] { std::cout << "Task 2" << std::endl; }); manual->post([] { std::cout << "Task 3" << std::endl; }); std::cout << "Queued tasks: " << manual->size() << std::endl; // Execute one task at a time manual->loop_once(); std::cout << "After loop_once, remaining: " << manual->size() << std::endl; // Execute up to N tasks size_t executed = manual->loop(10); std::cout << "Executed " << executed << " tasks" << std::endl; // Wait for task with timeout manual->post([] { std::cout << "Delayed task" << std::endl; }); bool had_task = manual->loop_once_for(std::chrono::milliseconds(100)); std::cout << "Had task: " << (had_task ? "yes" : "no") << std::endl; // Check if empty std::cout << "Is empty: " << (manual->empty() ? "yes" : "no") << std::endl; return 0; } // Output: // Queued tasks: 3 // Task 1 // After loop_once, remaining: 2 // Task 2 // Task 3 // Executed 2 tasks // Delayed task // Had task: yes // Is empty: yes ``` -------------------------------- ### Schedule Timers in C++ Source: https://context7.com/david-haim/concurrencpp/llms.txt Shows the usage of timer queues to schedule periodic and one-shot callbacks on specific executors. ```cpp #include "concurrencpp/concurrencpp.h" #include #include using namespace std::chrono_literals; int main() { concurrencpp::runtime runtime; auto timer_queue = runtime.timer_queue(); auto executor = runtime.thread_pool_executor(); std::atomic counter{0}; // Regular timer: fires after due_time, then every frequency concurrencpp::timer regular_timer = timer_queue->make_timer( 500ms, // due time: first fire after 500ms 200ms, // frequency: then every 200ms executor, [&counter] { std::cout << "Timer fired: " << ++counter << std::endl; } ); std::this_thread::sleep_for(1500ms); // Cancel the timer regular_timer.cancel(); std::cout << "Timer cancelled" << std::endl; // One-shot timer: fires once after due_time concurrencpp::timer oneshot = timer_queue->make_one_shot_timer( 300ms, executor, [] { std::cout << "One-shot timer fired!" << std::endl; } ); std::this_thread::sleep_for(500ms); // Timer properties std::cout << "Oneshot due time: " << oneshot.get_due_time().count() << "ms" << std::endl; return 0; } // Output: // Timer fired: 1 // Timer fired: 2 // Timer fired: 3 // Timer fired: 4 // Timer fired: 5 // Timer cancelled // One-shot timer fired! // Oneshot due time: 300ms ``` -------------------------------- ### Monitoring Thread Lifecycle Source: https://context7.com/david-haim/concurrencpp/llms.txt Configure runtime options to attach callbacks for thread creation and termination events. ```cpp #include "concurrencpp/concurrencpp.h" #include int main() { concurrencpp::runtime_options options; options.thread_started_callback = [](std::string_view name) { std::cout << "[START] Thread: " << name << ", ID: " << std::this_thread::get_id() << std::endl; }; options.thread_terminated_callback = [](std::string_view name) { std::cout << "[END] Thread: " << name << ", ID: " << std::this_thread::get_id() << std::endl; }; concurrencpp::runtime runtime(options); // Trigger thread creation auto result = runtime.thread_pool_executor()->submit([] { return 42; }); result.get(); // Small delay to see thread activity std::this_thread::sleep_for(std::chrono::milliseconds(100)); return 0; } ``` -------------------------------- ### Create a Ready Lazy Result with when_all (No Inputs) Source: https://github.com/david-haim/concurrencpp/blob/master/README.md Use `when_all` with no input results to create a ready lazy result. This is useful when an operation needs to satisfy a `when_all` requirement but has no asynchronous tasks to wait for. ```cpp lazy_result> when_all(std::shared_ptr resume_executor); ``` -------------------------------- ### Submit and Post Tasks to Executors Source: https://context7.com/david-haim/concurrencpp/llms.txt Schedule tasks using submit for result tracking or post for fire-and-forget execution, including bulk submission support. ```cpp #include "concurrencpp/concurrencpp.h" #include int main() { concurrencpp::runtime runtime; auto executor = runtime.thread_pool_executor(); // submit: returns result object to get the return value auto result = executor->submit([]() -> int { return 42; }); // Block and get the result int value = result.get(); std::cout << "Result: " << value << std::endl; // post: fire-and-forget, no result tracking executor->post([] { std::cout << "Task executed!" << std::endl; }); // submit with arguments auto result2 = executor->submit([](int a, int b) { return a + b; }, 10, 20); std::cout << "Sum: " << result2.get() << std::endl; // bulk_submit: submit multiple tasks at once std::vector> tasks = { [] { return 1; }, [] { return 2; }, [] { return 3; } }; auto results = executor->bulk_submit(std::span(tasks)); for (auto& r : results) { std::cout << "Bulk result: " << r.get() << std::endl; } return 0; } // Output: // Result: 42 // Task executed! // Sum: 30 // Bulk result: 1 // Bulk result: 2 // Bulk result: 3 ``` -------------------------------- ### Implement a custom logging executor in C++ Source: https://github.com/david-haim/concurrencpp/blob/master/README.md Create a user-defined executor by inheriting from concurrencpp::derivable_executor and registering it via runtime::make_executor. ```cpp #include "concurrencpp/concurrencpp.h" #include #include #include #include #include class logging_executor : public concurrencpp::derivable_executor { private: mutable std::mutex _lock; std::queue _queue; std::condition_variable _condition; bool _shutdown_requested; std::thread _thread; const std::string _prefix; void work_loop() { while (true) { std::unique_lock lock(_lock); if (_shutdown_requested) { return; } if (!_queue.empty()) { auto task = std::move(_queue.front()); _queue.pop(); lock.unlock(); std::cout << _prefix << " A task is being executed" << std::endl; task(); continue; } _condition.wait(lock, [this] { return !_queue.empty() || _shutdown_requested; }); } } public: logging_executor(std::string_view prefix) : derivable_executor("logging_executor"), _shutdown_requested(false), _prefix(prefix) { _thread = std::thread([this] { work_loop(); }); } void enqueue(concurrencpp::task task) override { std::cout << _prefix << " A task is being enqueued!" << std::endl; std::unique_lock lock(_lock); if (_shutdown_requested) { throw concurrencpp::errors::runtime_shutdown("logging executor - executor was shutdown."); } _queue.emplace(std::move(task)); _condition.notify_one(); } void enqueue(std::span tasks) override { std::cout << _prefix << tasks.size() << " tasks are being enqueued!" << std::endl; std::unique_lock lock(_lock); if (_shutdown_requested) { throw concurrencpp::errors::runtime_shutdown("logging executor - executor was shutdown."); } for (auto& task : tasks) { _queue.emplace(std::move(task)); } _condition.notify_one(); } int max_concurrency_level() const noexcept override { return 1; } bool shutdown_requested() const noexcept override { std::unique_lock lock(_lock); return _shutdown_requested; } void shutdown() noexcept override { std::cout << _prefix << " shutdown requested" << std::endl; std::unique_lock lock(_lock); if (_shutdown_requested) return; //nothing to do. _shutdown_requested = true; lock.unlock(); _condition.notify_one(); _thread.join(); } }; int main() { concurrencpp::runtime runtime; auto logging_ex = runtime.make_executor("Session #1234"); for (size_t i = 0; i < 10; i++) { logging_ex->post([] { std::cout << "hello world" << std::endl; }); } std::getchar(); return 0; } ``` -------------------------------- ### make_ready_result Utility Function Source: https://github.com/david-haim/concurrencpp/blob/master/README.md Documentation for the `make_ready_result` function, which creates a pre-completed result object. ```APIDOC ## `make_ready_result` Function ### Description The `make_ready_result` function constructs a `result` object that is already in a completed state. Awaiting such a result will cause the current coroutine to resume immediately. The `get` method and `operator co_await` will return the value used to construct the result. ### Overloads 1. **For non-void types:** ```cpp template result make_ready_result(argument_types&& ... arguments); ``` This overload creates a ready result object by constructing an object of `type` in-place using the provided `arguments`. It may throw exceptions that the constructor of `type` throws, or `std::bad_alloc` if memory allocation fails. 2. **For `void` type:** ```cpp result make_ready_result(); ``` This overload creates a ready result object for a `void` type. It may throw `std::bad_alloc` if memory allocation fails. ``` -------------------------------- ### Wait for multiple async operations with when_all and when_any Source: https://context7.com/david-haim/concurrencpp/llms.txt Demonstrates waiting for multiple tasks using variadic arguments or iterator ranges. when_all returns a tuple of results, while when_any returns the index and results of the first completed task. ```cpp #include "concurrencpp/concurrencpp.h" #include #include using namespace concurrencpp; result demonstrate_when_all(std::shared_ptr executor) { // Create multiple async tasks auto r1 = executor->submit([] { return 1; }); auto r2 = executor->submit([] { return 2; }); auto r3 = executor->submit([] { return 3; }); // Wait for all - returns tuple of ready results auto all_results = co_await when_all(executor, std::move(r1), std::move(r2), std::move(r3)); // Access results from tuple auto [res1, res2, res3] = std::move(all_results); std::cout << "All results: " << res1.get() << ", " << res2.get() << ", " << res3.get() << std::endl; // when_all with iterator range std::vector> results; for (int i = 0; i < 5; i++) { results.push_back(executor->submit([i] { return i * 10; })); } auto vec_results = co_await when_all(executor, results.begin(), results.end()); std::cout << "Vector results: "; for (auto& r : vec_results) { std::cout << r.get() << " "; } std::cout << std::endl; } result demonstrate_when_any(std::shared_ptr executor) { auto r1 = executor->submit([] { return 100; }); auto r2 = executor->submit([] { return 200; }); auto r3 = executor->submit([] { return 300; }); // Wait for any one result auto any_result = co_await when_any(executor, std::move(r1), std::move(r2), std::move(r3)); std::cout << "First completed index: " << any_result.index << std::endl; // Get the completed result from the tuple auto& [completed_r1, completed_r2, completed_r3] = any_result.results; } int main() { concurrencpp::runtime runtime; auto executor = runtime.thread_pool_executor(); demonstrate_when_all(executor).get(); demonstrate_when_any(executor).get(); return 0; } // Output: // All results: 1, 2, 3 // Vector results: 0 10 20 30 40 // First completed index: 0 ``` -------------------------------- ### Runtime Object Lifecycle and Usage Source: https://github.com/david-haim/concurrencpp/blob/master/README.md Explains the role of the runtime object, its creation, destruction, and how it manages executors and timers. It highlights the RAII pattern for resource management. ```APIDOC ## Runtime Object The concurrencpp runtime object is the agent used to acquire, store and create new executors. The runtime must be created as a value type as soon as the main function starts to run. When 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. Upon 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. This frees concurrent applications from needing to communicate termination messages explicitly. Tasks are free use executors as long as the runtime object is alive. ``` -------------------------------- ### Manual result completion with result_promise Source: https://context7.com/david-haim/concurrencpp/llms.txt Shows how to manually fulfill a result object using result_promise, including handling exceptions and using set_from_function for convenience. ```cpp #include "concurrencpp/concurrencpp.h" #include #include int main() { // Create a promise and get associated result concurrencpp::result_promise promise; auto result = promise.get_result(); // Simulate async operation with callback std::thread worker([promise = std::move(promise)]() mutable { std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Complete the promise with a value promise.set_result("Hello from async operation!"); }); // Block until result is ready std::cout << result.get() << std::endl; worker.join(); // Promise with exception concurrencpp::result_promise error_promise; auto error_result = error_promise.get_result(); std::thread error_worker([promise = std::move(error_promise)]() mutable { try { throw std::runtime_error("Operation failed"); } catch (...) { promise.set_exception(std::current_exception()); } }); try { error_result.get(); } catch (const std::exception& e) { std::cout << "Caught: " << e.what() << std::endl; } error_worker.join(); // set_from_function: convenience method concurrencpp::result_promise func_promise; auto func_result = func_promise.get_result(); func_promise.set_from_function([](int a, int b) { return a * b; }, 6, 7); std::cout << "Function result: " << func_result.get() << std::endl; return 0; } // Output: // Hello from async operation! // Caught: Operation failed // Function result: 42 ``` -------------------------------- ### Define when_any utility functions Source: https://github.com/david-haim/concurrencpp/blob/master/README.md Provides the structure for when_any_result and the function signatures for heterogeneous and iterator-based result handling. ```cpp /* Helper struct returned from when_any. index is the position of the ready result in results sequence. results is either an std::tuple or an std::vector of the results that were passed to when_any. */ template struct when_any_result { std::size_t index; sequence_type results; }; /* Creates a result object that becomes ready when at least one of the input results is ready. Passed result objects are emptied and returned as a tuple. Throws std::invalid_argument if any of the passed result objects is empty. Might throw an std::bad_alloc exception if no memory is available. */ template lazy_result>> when_any(std::shared_ptr resume_executor, result_types&& ... results); /* Overload. Similar to when_any(result_types&& ...) but receives a pair of iterators referencing a range. Passed result objects are emptied and returned as a vector. Throws std::invalid_argument if begin == end. Throws std::invalid_argument if any of the passed result objects is empty. Might throw an std::bad_alloc exception if no memory is available. */ template lazy_result::value_type>>> when_any(std::shared_ptr resume_executor, iterator_type begin, iterator_type end); ``` -------------------------------- ### CMake Build Configuration for concurrencpp Source: https://github.com/david-haim/concurrencpp/blob/master/example/6_manual_executor/CMakeLists.txt This CMakeLists.txt file configures the build for an executable that uses the concurrencpp library. It fetches the library, sets C++20 standard, and links the library to the executable. ```cmake cmake_minimum_required(VERSION 3.16) project(6_manual_executor LANGUAGES CXX) include(FetchContent) FetchContent_Declare(concurrencpp SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") FetchContent_MakeAvailable(concurrencpp) include(../../cmake/coroutineOptions.cmake) add_executable(6_manual_executor source/main.cpp) target_compile_features(6_manual_executor PRIVATE cxx_std_20) target_link_libraries(6_manual_executor PRIVATE concurrencpp::concurrencpp) target_coroutine_options(6_manual_executor) ``` -------------------------------- ### Calculate Fibonacci with Executor Submission Source: https://github.com/david-haim/concurrencpp/blob/master/README.md Demonstrates an alternative approach using executor::submit, which requires double co_await due to the nested result type. ```cpp #include "concurrencpp/concurrencpp.h" #include using namespace concurrencpp; int fibonacci_sync(int i) { if (i == 0) { return 0; } if (i == 1) { return 1; } return fibonacci_sync(i - 1) + fibonacci_sync(i - 2); } result fibonacci(std::shared_ptr tpe, const int curr) { if (curr <= 10) { co_return fibonacci_sync(curr); } auto fib_1 = tpe->submit(fibonacci, tpe, curr - 1); auto fib_2 = tpe->submit(fibonacci, tpe, curr - 2); co_return co_await co_await fib_1 + co_await co_await fib_2; } int main() { concurrencpp::runtime runtime; auto fibb_30 = fibonacci(runtime.thread_pool_executor(), 30).get(); std::cout << "fibonacci(30) = " << fibb_30 << std::endl; return 0; } ``` -------------------------------- ### Monitor Thread Creation and Termination with Callbacks Source: https://github.com/david-haim/concurrencpp/blob/master/README.md Set thread_started_callback and thread_terminated_callback in runtime_options to receive notifications when threads are created or terminated. Callbacks are executed within the respective thread and receive a thread name and ID. ```cpp #include "concurrencpp/concurrencpp.h" #include int main() { concurrencpp::runtime_options options; options.thread_started_callback = [](std::string_view thread_name) { std::cout << "A new thread is starting to run, name: " << thread_name << ", thread id: " << std::this_thread::get_id() << std::endl; }; options.thread_terminated_callback = [](std::string_view thread_name) { std::cout << "A thread is terminating, name: " << thread_name << ", thread id: " << std::this_thread::get_id() << std::endl; }; concurrencpp::runtime runtime(options); const auto timer_queue = runtime.timer_queue(); const auto thread_pool_executor = runtime.thread_pool_executor(); concurrencpp::timer timer = timer_queue->make_timer(std::chrono::milliseconds(100), std::chrono::milliseconds(500), thread_pool_executor, [] { std::cout << "A timer callable is executing" << std::endl; }); std::this_thread::sleep_for(std::chrono::seconds(3)); return 0; } ``` -------------------------------- ### Creating Ready and Exceptional Results Source: https://context7.com/david-haim/concurrencpp/llms.txt Factory functions for generating result objects that are immediately ready or contain an exception, useful for caching or error handling. ```cpp #include "concurrencpp/concurrencpp.h" #include using namespace concurrencpp; result cached_or_compute(bool use_cache) { if (use_cache) { // Return immediately ready result co_return co_await make_ready_result(42); } // Simulate computation co_return 100; } result may_fail(bool should_fail) { if (should_fail) { co_return co_await make_exceptional_result( std::runtime_error("Operation failed") ); } co_return 200; } int main() { // Ready result - no async overhead auto ready = make_ready_result("Hello"); std::cout << ready.get() << std::endl; // Void ready result auto void_ready = make_ready_result(); void_ready.get(); // Returns immediately // Exceptional result auto exceptional = make_exceptional_result( std::make_exception_ptr(std::logic_error("Error!")) ); try { exceptional.get(); } catch (const std::logic_error& e) { std::cout << "Caught: " << e.what() << std::endl; } // Use in coroutines std::cout << "Cached: " << cached_or_compute(true).get() << std::endl; std::cout << "Computed: " << cached_or_compute(false).get() << std::endl; return 0; } ```