├── .clang-format ├── .gitignore ├── .vscode ├── c_cpp_properties.json ├── launch.json └── tasks.json ├── CMakeLists.txt ├── Chapter_03 ├── 3x01-threads_creation.cpp ├── 3x02-checking_hardware_concurrency.cpp ├── 3x03-stream_writing_with_cout.cpp ├── 3x04-stream_writing_with_ostringstream.cpp ├── 3x05-stream_writing_with_osynctream.cpp ├── 3x06-identifying_a_thread.cpp ├── 3x07-passing_arguments.cpp ├── 3x08-returning_values.cpp ├── 3x09-moving_threads.cpp ├── 3x10-checking_thread_joinable.cpp ├── 3x11-daemon_thread_by_detaching.cpp ├── 3x12-jthreads.cpp ├── 3x13-jthreads_constructors_destructors.cpp ├── 3x14-yielding_thread_execution.cpp ├── 3x15-thread_cancellation_with_atomic_check.cpp ├── 3x16-thread_cancellation_with_stop_token.cpp ├── 3x17-catching_exceptions.cpp ├── 3x18-thread_local_storage.cpp ├── 3x19-timer.cpp └── CMakeLists.txt ├── Chapter_04 ├── 4x01-counter.cpp ├── 4x02-mutex.cpp ├── 4x03-incorrect_mutex_lock.cpp ├── 4x04-mutex_queue.cpp ├── 4x05-call_once.cpp ├── 4x06-semaphore_queue.cpp ├── 4x07-latches_and_barriers.cpp ├── 4x08-shared_mutex.cpp ├── 4x09-try_lock_for.cpp ├── 4x10-condition_variable.cpp ├── CMakeLists.txt ├── mutex_queue.h └── semaphore_queue.h ├── Chapter_05 ├── 5x01-atomic_flag.cpp ├── 5x02-thread_progress.cpp ├── 5x03-simple-statistics.cpp ├── 5x04-atomic_type_not_lock-free.cpp ├── 5x05-lazy-on-time-initialization.cpp ├── 5x06-atomics_synchronization.cpp ├── 5x07-sequential_consistency.cpp ├── 5x08-acquire_release.cpp ├── 5x09-SPSC_lock_free_queue.cpp └── CMakeLists.txt ├── Chapter_06 ├── 6x01-promise_set_value_param.cpp ├── 6x02-promise_set_value_lambda.cpp ├── 6x03-promise_set_value_as_barrier.cpp ├── 6x04-empty_future_get_throws.cpp ├── 6x05-shared_future.cpp ├── 6x06-packaged_tasks.cpp ├── 6x07-packaged_tasks_ready_at_exit.cpp ├── 6x08-packaged_tasks_reset.cpp ├── 6x09-cancelling-tasks.cpp ├── 6x10-combined_results.cpp ├── 6x11-pipeline.cpp ├── 6x12-producer_consumer.cpp └── CMakeLists.txt ├── Chapter_07 ├── 7x01-async_function_invocation.cpp ├── 7x02-async_passing_values.cpp ├── 7x03-async_returning_values.cpp ├── 7x04-async_launch_policies.cpp ├── 7x05-async_handling_exceptions.cpp ├── 7x06-async_future_behavior.cpp ├── 7x07-thread-limiber.cpp ├── 7x08-data_aggregation.cpp ├── 7x09-async_search_across_containers.cpp ├── 7x10-async_search_large_vector.cpp ├── 7x11-async_matrix_multiplication.cpp ├── 7x12-chaining_tasks.cpp ├── 7x13-pipeline.cpp └── CMakeLists.txt ├── Chapter_08 ├── 8x01-simple_coroutine.cpp ├── 8x02-yield_coroutine.cpp ├── 8x03-waiting_coroutine.cpp ├── 8x04-fibonacci_generator.cpp ├── 8x05-generator_cpp23.cpp ├── 8x06-integer_parser.cpp └── CMakeLists.txt ├── Chapter_09 ├── 9x01-io_context_run.cpp ├── 9x02-executor_work_guard.cpp ├── 9x03-chained_asynchronous_tasks.cpp ├── 9x04-post_and_dispatch.cpp ├── 9x05-future_exception_handling.cpp ├── 9x06-exception_handling.cpp ├── 9x07-single_threaded.cpp ├── 9x08-multithreaded_io_context_main_thread.cpp ├── 9x09-multithreaded_io_context_own_thread.cpp ├── 9x10-single_io_context_several_handlers_thread.cpp ├── 9x11-multithreaded_single_io_context_run_different_threads.cpp ├── 9x12-objects_lifetime.cpp ├── 9x13-scatter_gather.cpp ├── 9x14-signal_handling.cpp ├── 9x15-canceling_operations.cpp ├── 9x16-cancellation_slot.cpp ├── 9x17-implicit_strands.cpp ├── 9x18-explicit_strands.cpp ├── 9x19-coroutines.cpp └── CMakeLists.txt ├── Chapter_10 ├── 10x01-hello_boost_cobalt.cpp ├── 10x02-basic_generator.cpp ├── 10x03-input_output_generator.cpp ├── 10x04-input_output_lazy_generator.cpp ├── 10x05-fibonacci_generator.cpp ├── 10x06-simple_promise.cpp ├── 10x07-promise_task_difference.cpp ├── 10x08-channels.cpp ├── 10x09-cobalt_join_gather.cpp └── CMakeLists.txt ├── Chapter_11 ├── 11x01-logging.cpp ├── 11x02-debug_deadlock.cpp ├── 11x03-debug_race_condition.cpp ├── 11x04-debug_coroutines.cpp └── CMakeLists.txt ├── Chapter_12 ├── 12x01-ASAN_heap_use_after_free.cpp ├── 12x02-ASAN_stack_use_after_return.cpp ├── 12x03-ASAN_stack_user_after_scope.cpp ├── 12x04-ASAN_heap_buffer_overflow.cpp ├── 12x05-ASAN_stack_buffer_overflow.cpp ├── 12x06-ASAN_global_buffer_overflow.cpp ├── 12x07-LSAN_missing_delete.cpp ├── 12x08-TSAN_data_race_global_variable.cpp ├── 12x09-TSAN_data_race_stl_map.cpp ├── 12x10-TSAN_data_race_stl_map_pointer.cpp ├── 12x11-TSAN_non_atomic_reference_counting.cpp ├── 12x12-TSAN_data_race_pointer_creation.cpp ├── 12x13-UBSAN-uninitialized_memory.cpp ├── 12x14-MSAN-uninitialized_memory.cpp ├── 12x15-testing_async_function.cpp ├── 12x16-testing_async_functoin_with_timeout.cpp ├── 12x17-testing_callbacks.cpp ├── 12x18-testing_with_dependency_injection.cpp ├── 12x19-testing_async_function_with_mocking.cpp ├── 12x20-testing_exceptions.cpp ├── 12x21-testing_async_function_with_multiple_threads.cpp ├── 12x22-testing_async_function_with_event_loop.cpp ├── 12x23-testing_coroutines.cpp ├── 12x24-stress_testing.cpp └── CMakeLists.txt ├── Chapter_13 ├── 13x01-chrono_profile.cpp ├── 13x02-benchmark_vector.cpp ├── 13x03-benchmark_sort.cpp ├── 13x04-benchmark_threads.cpp ├── 13x05-sort_perf.cpp ├── 13x06-false_sharing.cpp ├── 13x07-thread_contention.cpp ├── 13x08-spsp_lock_free_queue.cpp └── CMakeLists.txt ├── LICENSE ├── README.md └── scripts └── install_compilers.sh /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | IndentWidth: 4 3 | ColumnLimit: 120 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/* 2 | build/* 3 | CMakeFiles 4 | Makefile 5 | *.cmake 6 | CMakeCache.txt 7 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux-gcc", 5 | "includePath": [ 6 | "${workspaceFolder}/**" 7 | ], 8 | "defines": [], 9 | "compilerPath": "/usr/bin/g++", 10 | "cStandard": "c17", 11 | "intelliSenseMode": "linux-gcc-x64", 12 | "cppStandard": "c++20", 13 | "compilerArgs": [ 14 | "-std=c++20" 15 | ] 16 | } 17 | ], 18 | "version": 4 19 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "C/C++: g++ build and debug active file", 5 | "type": "cppdbg", 6 | "request": "launch", 7 | "program": "${fileDirname}/${fileBasenameNoExtension}", 8 | "args": [], 9 | "stopAtEntry": false, 10 | "cwd": "${fileDirname}", 11 | "environment": [], 12 | "externalConsole": false, 13 | "MIMode": "gdb", 14 | "setupCommands": [ 15 | { 16 | "description": "Enable pretty-printing for gdb", 17 | "text": "-enable-pretty-printing", 18 | "ignoreFailures": true 19 | }, 20 | { 21 | "description": "Set Disassembly Flavor to Intel", 22 | "text": "-gdb-set disassembly-flavor intel", 23 | "ignoreFailures": true 24 | } 25 | ], 26 | "preLaunchTask": "C/C++: g++ build active file", 27 | "miDebuggerPath": "/usr/bin/gdb" 28 | } 29 | ], 30 | "version": "2.0.0" 31 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": [ 3 | { 4 | "type": "cppbuild", 5 | "label": "C/C++: g++ build active file", 6 | "command": "/usr/bin/g++", 7 | "args": [ 8 | "-std=c++20", 9 | "-Wall", 10 | "-Wextra", 11 | "-pedantic", 12 | "-fdiagnostics-color=always", 13 | "-g", 14 | "${file}", 15 | "-I", "/usr/include/boost/", 16 | "-L/usr/local/lib/", 17 | "-lboost_thread", 18 | "-o", "${fileDirname}/${fileBasenameNoExtension}" 19 | ], 20 | "options": { 21 | "cwd": "${fileDirname}" 22 | }, 23 | "problemMatcher": [ 24 | "$gcc" 25 | ], 26 | "group": { 27 | "kind": "build", 28 | "isDefault": true 29 | }, 30 | "detail": "Task generated by Debugger." 31 | } 32 | ], 33 | "version": "2.0.0" 34 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5..3.29) 2 | project("Asynchronous Programming in C++") 3 | 4 | # Set C++ standard 5 | set(CMAKE_CXX_STANDARD 20) 6 | set(CMAKE_CXX_STANDARD_REQUIRED True) 7 | 8 | # Find Boost 9 | find_package(Boost 1.86.0 REQUIRED COMPONENTS thread system container) 10 | if(NOT Boost_FOUND) 11 | message(FATAL_ERROR "Boost 1.86.0 is required but not found.") 12 | endif() 13 | 14 | # Include Boost directories 15 | include_directories(${Boost_INCLUDE_DIRS}) 16 | 17 | # Find GoogleTest 18 | enable_testing() 19 | find_package(GTest REQUIRED) 20 | if(NOT GTest_FOUND) 21 | message(FATAL_ERROR "GoogleTest library is required but not found.") 22 | endif() 23 | include(GoogleTest) 24 | 25 | # Include GoogleTest directories 26 | include_directories(${GTest_INCLUDE_DIRS}) 27 | 28 | # Add {fmt} library 29 | find_package(fmt) 30 | if(NOT fmt_FOUND) 31 | message(FATAL_ERROR "fmt library is required but not found.") 32 | endif() 33 | 34 | # Linker flags 35 | if((CMAKE_CXX_COMPILER_ID MATCHES GNU) OR (CMAKE_CXX_COMPILER_ID MATCHES Clang)) 36 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS} -O0 -g3 -Wall -Wextra -pedantic") 37 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} -O3 -DNDEBUG -Wall -Wextra -pedantic") 38 | endif() 39 | 40 | # Set the output directory for binaries 41 | set(BINARY_DIR ${CMAKE_BINARY_DIR}/bin) 42 | 43 | # Add chapters 44 | add_subdirectory(Chapter_03) 45 | add_subdirectory(Chapter_04) 46 | add_subdirectory(Chapter_05) 47 | add_subdirectory(Chapter_06) 48 | add_subdirectory(Chapter_07) 49 | add_subdirectory(Chapter_08) 50 | add_subdirectory(Chapter_09) 51 | add_subdirectory(Chapter_10) 52 | add_subdirectory(Chapter_11) 53 | add_subdirectory(Chapter_12) 54 | add_subdirectory(Chapter_13) 55 | -------------------------------------------------------------------------------- /Chapter_03/3x01-threads_creation.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | 5 | void func() { 6 | std::cout << "Using function pointer" << std::endl; 7 | } 8 | 9 | class FuncObjectClass { 10 | public: 11 | void operator()() { 12 | std::cout << "Using function object class" << std::endl; 13 | } 14 | }; 15 | 16 | int main() { 17 | // Start thread 1 using a function pointer. 18 | std::thread t1(func); 19 | 20 | // Start thread 2 using a lambda function 21 | auto lambda_func = []() { 22 | std::cout << "Using lambda function\n"; 23 | }; 24 | std::thread t2(lambda_func); 25 | 26 | // Start thread 3 using an embedded lambda function 27 | std::thread t3([]() { 28 | std::cout << "Using embedded lambda function\n"; 29 | }); 30 | 31 | // Start thread 4 using a function object (overloading operator() ) 32 | // FuncObjectClass obj1; 33 | std::thread t4{FuncObjectClass()}; 34 | 35 | // Start thread 5 using a non-static member function 36 | class Obj { 37 | public: 38 | void func() { 39 | std::cout << "Using a non-static member function\n"; 40 | } 41 | static void static_func() { 42 | std::cout << "Using a static member function\n"; 43 | } 44 | }; 45 | 46 | Obj obj2; 47 | 48 | // In this case, the first argument is the reference to the function 49 | // and second is the object reference. 50 | std::thread t5(&Obj::func, &obj2); 51 | 52 | // Start thread 6 using a static member function. Only the first argument is needed. 53 | std::thread t6(&Obj::static_func); 54 | 55 | // Wait threads to finish 56 | t1.join(); 57 | t2.join(); 58 | t3.join(); 59 | t4.join(); 60 | t5.join(); 61 | t6.join(); 62 | 63 | return 0; 64 | } -------------------------------------------------------------------------------- /Chapter_03/3x02-checking_hardware_concurrency.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() { 6 | const auto processor_count = std::thread::hardware_concurrency(); 7 | std::cout << "Processors: " << processor_count << std::endl; 8 | 9 | const auto nthreads = boost::thread::hardware_concurrency(); 10 | std::cout << "Processors: " << nthreads << std::endl; 11 | 12 | return 0; 13 | } -------------------------------------------------------------------------------- /Chapter_03/3x03-stream_writing_with_cout.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | std::thread t1([]() { 6 | for (int i = 0; i < 100; ++i) { 7 | std::cout << "1 " << "2 " << "3 " << "4 " << std::endl; 8 | } 9 | }); 10 | 11 | std::thread t2([]() { 12 | for (int i = 0; i < 100; ++i) { 13 | std::cout << "5 " << "6 " << "7 " << "8 " << std::endl; 14 | } 15 | }); 16 | 17 | t1.join(); 18 | t2.join(); 19 | 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /Chapter_03/3x04-stream_writing_with_ostringstream.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() { 6 | std::thread t1([]() { 7 | for (int i = 0; i < 100; ++i) { 8 | std::ostringstream oss; 9 | oss << "1 " << "2 " << "3 " << "4 " << "\n"; 10 | std::cout << oss.str(); 11 | } 12 | }); 13 | 14 | std::thread t2([]() { 15 | for (int i = 0; i < 100; ++i) { 16 | std::ostringstream oss; 17 | oss << "5 " << "6 " << "7 " << "8 " << "\n"; 18 | std::cout << oss.str(); 19 | } 20 | }); 21 | 22 | t1.join(); 23 | t2.join(); 24 | 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /Chapter_03/3x05-stream_writing_with_osynctream.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define sync_cout std::osyncstream(std::cout) 6 | 7 | int main() { 8 | 9 | std::thread t1([]() { 10 | for (int i = 0; i < 100; ++i) { 11 | sync_cout << "1 " << "2 " << "3 " << "4 " << std::endl; 12 | } 13 | }); 14 | 15 | std::thread t2([]() { 16 | for (int i = 0; i < 100; ++i) { 17 | sync_cout << "5 " << "6 " << "7 " << "8 " << std::endl; 18 | } 19 | }); 20 | 21 | t1.join(); 22 | t2.join(); 23 | 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /Chapter_03/3x06-identifying_a_thread.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std::chrono_literals; 6 | 7 | void func() { 8 | std::this_thread::sleep_for(1s); 9 | } 10 | 11 | int main() { 12 | std::thread t(func); 13 | std::cout << "Thread ID: " << t.get_id() << std::endl; 14 | t.join(); 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /Chapter_03/3x07-passing_arguments.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define sync_cout std::osyncstream(std::cout) 8 | 9 | // Passing arguments by value 10 | void funcByValue(const std::string& str, int val) { 11 | sync_cout << "str: " << str << ", val: " << val << std::endl; 12 | } 13 | 14 | // Passing arguments by reference 15 | void modifyValues(std::string& str, int& val) { 16 | str += " (Thread)"; 17 | val++; 18 | } 19 | 20 | void printVector(const std::vector& v) { 21 | sync_cout << "Vector: "; 22 | for (int num : v) { 23 | sync_cout << num << " "; 24 | } 25 | sync_cout << std::endl; 26 | } 27 | 28 | int main() { 29 | // Passing arguments by value 30 | std::string str1{"Passing by value"}; 31 | std::thread t1(funcByValue, str1, 1); 32 | t1.join(); 33 | 34 | // Passing arguments by reference 35 | std::string str2{"Passing by reference"}; 36 | int val = 1; 37 | std::thread t2(modifyValues, std::ref(str2), std::ref(val)); 38 | t2.join(); 39 | sync_cout << "str: " << str2 << ", val: " << val << std::endl; 40 | 41 | // Passing argument by const reference 42 | std::vector v{1, 2, 3, 4, 5}; 43 | std::thread t3(printVector, std::cref(v)); 44 | t3.join(); 45 | 46 | // Moving element into a thread 47 | std::thread t4(printVector, std::move(v)); 48 | t4.join(); 49 | 50 | // Note: Trying to access v here would result in undefined behavior 51 | // as V was moved into the thread and not usable anymore. 52 | 53 | // Lambda function with captures 54 | std::string str5{"Hello"}; 55 | std::thread t5([&]() { 56 | sync_cout << "str: " << str5 << std::endl; 57 | }); 58 | t5.join(); 59 | 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /Chapter_03/3x08-returning_values.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define sync_cout std::osyncstream(std::cout) 9 | 10 | using namespace std::chrono_literals; 11 | 12 | namespace { 13 | int result = 0; 14 | std::mutex mtx; 15 | }; 16 | 17 | void func(int& result) { 18 | // Simulate some work and return random value in range [1,10] 19 | std::this_thread::sleep_for(1s); 20 | result = 1 + (rand() % 10); 21 | } 22 | 23 | void funcWithMutex() { 24 | // Simulating some computation 25 | std::this_thread::sleep_for(1s); 26 | int localVar = 1 + (rand() % 10); 27 | 28 | // Lock the mutex before updating the shared variable 29 | std::lock_guard lock(mtx); 30 | result = localVar; 31 | } 32 | 33 | int main() { 34 | // Get result value by reference 35 | std::thread t1(func, std::ref(result)); 36 | t1.join(); 37 | sync_cout << "T1 result: " << result << std::endl; 38 | 39 | // Get result value by lambda capture 40 | std::thread t2([&]() { func(result); }); 41 | t2.join(); 42 | sync_cout << "T2 result: " << result << std::endl; 43 | 44 | // Get result value by shared variable and mutex 45 | std::thread t3(funcWithMutex); 46 | t3.join(); 47 | sync_cout << "T3 result: " << result << std::endl; 48 | 49 | return 0; 50 | } 51 | -------------------------------------------------------------------------------- /Chapter_03/3x09-moving_threads.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define sync_cout std::osyncstream(std::cout) 7 | 8 | using namespace std::chrono_literals; 9 | 10 | void func() { 11 | for (auto i = 0; i < 10; ++i) { 12 | sync_cout << "Thread ID: " << std::this_thread::get_id() << " is working." << std::endl; 13 | std::this_thread::sleep_for(500ms); 14 | } 15 | } 16 | 17 | int main() { 18 | std::thread t1(func); 19 | sync_cout << "T1 id: " << t1.get_id() << std::endl; 20 | std::this_thread::sleep_for(1s); 21 | 22 | // Thread 1 moved to thread 2. It keeps the same ID, and t1 is no longer joinable. 23 | std::thread t2 = std::move(t1); 24 | sync_cout << "T2 id: " << t2.get_id() << std::endl; 25 | std::this_thread::sleep_for(1s); 26 | 27 | sync_cout << "Are threads joinable? t1=" << t1.joinable() << " t2=" << t2.joinable() << std::endl; 28 | 29 | t2.join(); 30 | 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /Chapter_03/3x10-checking_thread_joinable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std::chrono_literals; 6 | 7 | void func() { 8 | std::this_thread::sleep_for(100ms); 9 | } 10 | 11 | int main() { 12 | std::thread t1; 13 | std::cout << "Is t1 joinable? " << t1.joinable() << std::endl; 14 | 15 | std::thread t2(func); 16 | t1.swap(t2); 17 | std::cout << "Is t1 joinable? " << t1.joinable() << std::endl; 18 | std::cout << "Is t2 joinable? " << t2.joinable() << std::endl; 19 | 20 | t1.join(); 21 | std::cout << "Is t1 joinable? " << t1.joinable() << std::endl; 22 | 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /Chapter_03/3x11-daemon_thread_by_detaching.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define sync_cout std::osyncstream(std::cout) 7 | 8 | using namespace std::chrono_literals; 9 | 10 | namespace { 11 | int timeout = 3; 12 | } 13 | 14 | void daemonThread() { 15 | sync_cout << "Daemon thread starting...\n"; 16 | while (timeout-- > 0) { 17 | sync_cout << "Daemon thread is running...\n"; 18 | std::this_thread::sleep_for(1s); 19 | } 20 | sync_cout << "Daemon thread exiting...\n"; 21 | } 22 | 23 | int main() { 24 | std::thread t(daemonThread); 25 | t.detach(); 26 | 27 | std::this_thread::sleep_for(std::chrono::seconds(timeout + 1)); 28 | 29 | sync_cout << "Main thread exiting...\n"; 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /Chapter_03/3x12-jthreads.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std::chrono_literals; 6 | 7 | void func() { 8 | std::this_thread::sleep_for(1s); 9 | } 10 | 11 | int main() { 12 | std::jthread t(func); 13 | std::cout << "Thread ID: " << t.get_id() << std::endl; 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /Chapter_03/3x13-jthreads_constructors_destructors.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define sync_cout std::osyncstream(std::cout) 8 | 9 | using namespace std::chrono_literals; 10 | 11 | class JthreadWrapper { 12 | public: 13 | JthreadWrapper(const std::function& func, const std::string& str) 14 | : t(func, str), name(str) { 15 | sync_cout << "Thread " << name << " being created" << std::endl; 16 | } 17 | 18 | ~JthreadWrapper() { 19 | sync_cout << "Thread " << name << " being destroyed" << std::endl; 20 | } 21 | 22 | private: 23 | std::jthread t; 24 | std::string name; 25 | }; 26 | 27 | void func(const std::string& name) { 28 | sync_cout << "Thread " << name << " starting..." << std::endl; 29 | std::this_thread::sleep_for(1s); 30 | sync_cout << "Thread " << name << " finishing..." << std::endl; 31 | } 32 | 33 | int main() { 34 | JthreadWrapper t1(func, "t1"); 35 | JthreadWrapper t2(func, "t2"); 36 | JthreadWrapper t3(func, "t3"); 37 | 38 | std::this_thread::sleep_for(2s); 39 | 40 | // t1, t2, t3 will be destroyed when main exits 41 | sync_cout << "Main thread exiting..." << std::endl; 42 | 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /Chapter_03/3x14-yielding_thread_execution.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define sync_cout std::osyncstream(std::cout) 8 | 9 | using namespace std::chrono_literals; 10 | 11 | namespace { 12 | std::mutex mtx; 13 | } 14 | 15 | int main() { 16 | auto work = [&](const std::string& name) { 17 | while (true) { 18 | bool work_to_do = rand() % 2; 19 | if (work_to_do) { 20 | // Do some work: Active wait for 3 second 21 | sync_cout << name << ": working\n"; 22 | std::lock_guard lock(mtx); 23 | auto start = std::chrono::steady_clock::now(); 24 | for (auto now = start; 25 | now < start + 3s; 26 | now = std::chrono::steady_clock::now()) { 27 | } 28 | } else { 29 | // Let other threads do some work 30 | sync_cout << name << ": yielding\n"; 31 | std::this_thread::yield(); 32 | } 33 | } 34 | }; 35 | 36 | // These threads will work forever as there is no exit condition in the work lambda 37 | std::jthread t1(work, "t1"); 38 | std::jthread t2(work, "t2"); 39 | 40 | return 0; 41 | } -------------------------------------------------------------------------------- /Chapter_03/3x15-thread_cancellation_with_atomic_check.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define sync_cout std::osyncstream(std::cout) 8 | 9 | using namespace std::chrono_literals; 10 | 11 | class Counter { 12 | using Callback = std::function; 13 | 14 | public: 15 | 16 | Counter(const Callback &callback) { 17 | sync_cout << "Counter: Starting..." << std::endl; 18 | t = std::jthread([&]() { 19 | while (running.load() == true) { 20 | sync_cout << "Counter: Running callback..." << std::endl; 21 | callback(); 22 | std::this_thread::sleep_for(1s); 23 | } 24 | sync_cout << "Counter: Exit" << std::endl; 25 | }); 26 | } 27 | 28 | void stop() { running.store(false); } 29 | 30 | private: 31 | std::jthread t; 32 | std::atomic_bool running{true}; 33 | }; 34 | 35 | int main() { 36 | // Create timer executing callback function every 500ms 37 | sync_cout << "Main: Create Counter\n"; 38 | Counter counter([]() { 39 | sync_cout << "Callback: Running...\n"; 40 | }); 41 | 42 | // Wait main thread for 3 seconds 43 | std::this_thread::sleep_for(3s); 44 | 45 | // Stop counter and wait for 1 second 46 | counter.stop(); 47 | std::this_thread::sleep_for(1s); 48 | 49 | sync_cout << "Main thread: Exit\n"; 50 | return 0; 51 | } 52 | -------------------------------------------------------------------------------- /Chapter_03/3x16-thread_cancellation_with_stop_token.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define sync_cout std::osyncstream(std::cout) 8 | 9 | using namespace std::chrono_literals; 10 | 11 | template 12 | void show_stop_props(std::string_view name, const T& stop_item) { 13 | sync_cout << std::boolalpha 14 | << name << ": stop_possible = " << stop_item.stop_possible() 15 | << ", stop_requested = " << stop_item.stop_requested() << '\n'; 16 | }; 17 | 18 | void func_with_stop_token(std::stop_token stop_token) { 19 | // Worker thread function that listens and acts on stop requests. 20 | for (int i = 0; i < 10; ++i) { 21 | std::this_thread::sleep_for(300ms); 22 | if (stop_token.stop_requested()) { 23 | sync_cout << "stop_worker: Stopping as requested\n"; 24 | return; 25 | } 26 | sync_cout << "stop_worker: Going back to sleep\n"; 27 | } 28 | } 29 | 30 | 31 | int main() { 32 | // A worker thread that will listen to stop requests 33 | // worker1 will be requested to stop from the main thread from token. 34 | auto worker1 = std::jthread(func_with_stop_token); 35 | std::stop_token stop_token = worker1.get_stop_token(); 36 | show_stop_props("stop_token", stop_token); 37 | 38 | // worker2 will be requested to stop from a stopper thread from source. 39 | auto worker2 = std::jthread(func_with_stop_token); 40 | std::stop_source stop_source = worker2.get_stop_source(); 41 | show_stop_props("stop_source", stop_source); 42 | 43 | // Register a stop callback on the worker1 thread. 44 | std::stop_callback callback(worker1.get_stop_token(), []{ 45 | sync_cout << "stop_callback for worker1 executed by thread: " << std::this_thread::get_id() << '\n'; 46 | }); 47 | 48 | // Stop_callback objects can be destroyed prematurely to prevent execution. 49 | // This scoped stop_callback will not execute. 50 | { 51 | std::stop_callback scoped_callback(worker2.get_stop_token(), []{ 52 | sync_cout << "stop_callback: Scoped stop callback will not execute\n"; 53 | }); 54 | } 55 | 56 | // Worker1: Request stop from main thread via stop_token 57 | sync_cout << "main_thread: Request stop and join worker1\n"; 58 | worker1.request_stop(); 59 | worker1.join(); 60 | show_stop_props("stop_token after request", stop_token); 61 | 62 | // Worker2: Request stop from stopper thread via stop_source 63 | sync_cout << "main_thread: Pass source to stopper thread\n"; 64 | auto stopper = std::thread( [](std::stop_source source) { 65 | std::this_thread::sleep_for(500ms); 66 | sync_cout << "stopper: Request stop for worker2 via source\n"; 67 | source.request_stop(); 68 | }, stop_source); 69 | 70 | stopper.join(); 71 | std::this_thread::sleep_for(200ms); 72 | show_stop_props("stop_source after request", stop_source); 73 | 74 | // After a stop has already been requested, a new stop_callback executes immediately. 75 | sync_cout << "main_thread: " << std::this_thread::get_id() << '\n'; 76 | std::stop_callback callback_after_stop(worker2.get_stop_token(), [] { 77 | sync_cout << "stop_callback for worker2 executed by thread: " << std::this_thread::get_id() << '\n'; 78 | }); 79 | } 80 | -------------------------------------------------------------------------------- /Chapter_03/3x17-catching_exceptions.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std::chrono_literals; 9 | 10 | std::exception_ptr captured_exception; 11 | std::mutex mtx; 12 | 13 | void func() { 14 | try { 15 | std::this_thread::sleep_for(1s); 16 | throw std::runtime_error("Error in func used within thread"); 17 | } catch (...) { 18 | // Capture exception into an atomic 19 | std::lock_guard lock(mtx); 20 | captured_exception = std::current_exception(); 21 | } 22 | } 23 | 24 | int main() { 25 | std::thread t(func); 26 | 27 | // Check for captured exception periodically 28 | while (!captured_exception) { 29 | std::this_thread::sleep_for(250ms); 30 | std::cout << "In main thread" << std::endl; 31 | } 32 | 33 | try { 34 | // Re-throw the exception 35 | std::rethrow_exception(captured_exception); 36 | } catch (const std::exception& e) { 37 | std::cerr << "Exception caught in main thread: " << e.what() << std::endl; 38 | } 39 | 40 | t.join(); 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /Chapter_03/3x18-thread_local_storage.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define sync_cout std::osyncstream(std::cout) 6 | 7 | thread_local int val = 0; 8 | 9 | void setValue(int newval) { val = newval; } 10 | 11 | void printValue() { sync_cout << val << ' '; } 12 | 13 | void multiplyByTwo(int arg) { 14 | // The thread_local value is set and multiplied by 2 15 | setValue(arg); 16 | val *= 2; 17 | printValue(); 18 | } 19 | 20 | int main() { 21 | val = 1; // Value in main thread 22 | 23 | // Each thread set its own value 24 | std::thread t1(multiplyByTwo, 1); 25 | std::thread t2(multiplyByTwo, 2); 26 | std::thread t3(multiplyByTwo, 3); 27 | 28 | t1.join(); 29 | t2.join(); 30 | t3.join(); 31 | 32 | std::cout << val << std::endl; 33 | 34 | return 0; 35 | } -------------------------------------------------------------------------------- /Chapter_03/3x19-timer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define sync_cout std::osyncstream(std::cout) 8 | 9 | using namespace std::chrono_literals; 10 | using namespace std::chrono; 11 | 12 | template 13 | class Timer { 14 | public: 15 | typedef std::function Callback; 16 | 17 | Timer(const Duration interval, const Callback& callback) { 18 | auto value = duration_cast(interval); 19 | sync_cout << "Timer: Starting with interval of " << value << std::endl; 20 | 21 | t = std::jthread([&](std::stop_token stop_token) { 22 | while (!stop_token.stop_requested()) { 23 | sync_cout << "Timer: Running callback " << val.load() << " ...\n"; 24 | val++; 25 | callback(); 26 | 27 | sync_cout << "Timer: Sleeping...\n"; 28 | std::this_thread::sleep_for(interval); 29 | } 30 | sync_cout << "Timer: Exit\n"; 31 | }); 32 | } 33 | 34 | void stop() { 35 | t.request_stop(); 36 | } 37 | 38 | private: 39 | std::jthread t; 40 | std::atomic_int32_t val{0}; 41 | }; 42 | 43 | int main(void) { 44 | // Create timer executing callback function every second 45 | sync_cout << "Main: Create timer\n"; 46 | Timer timer(1s, [&]() { 47 | sync_cout << "Callback: Running...\n"; 48 | }); 49 | 50 | // Wait main thread for 3 seconds 51 | std::this_thread::sleep_for(3s); 52 | 53 | // Stop timer 54 | sync_cout << "Main thread: Stop timer\n"; 55 | timer.stop(); 56 | 57 | // Wait main thread for 500ms while timer stops 58 | std::this_thread::sleep_for(500ms); 59 | sync_cout << "Main thread: Exit\n"; 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /Chapter_03/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Output directory 2 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BINARY_DIR}/Chapter_03) 3 | 4 | # Create executable for each cpp file 5 | file(GLOB SOURCES "*.cpp") 6 | foreach(SOURCE ${SOURCES}) 7 | get_filename_component(EXECUTABLE_NAME ${SOURCE} NAME_WE) 8 | add_executable(${EXECUTABLE_NAME} ${SOURCE}) 9 | target_link_libraries(${EXECUTABLE_NAME} ${Boost_LIBRARIES}) 10 | endforeach() 11 | -------------------------------------------------------------------------------- /Chapter_04/4x01-counter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | std::mutex mtx; 6 | int counter = 0; 7 | 8 | int main() { 9 | auto funcWithoutLocks = [] { 10 | for (int i = 0; i < 1000000; ++i) { 11 | ++counter; 12 | }; 13 | }; 14 | 15 | auto funcWithLocks = [] { 16 | for (int i = 0; i < 1000000; ++i) { 17 | mtx.lock(); 18 | ++counter; 19 | mtx.unlock(); 20 | }; 21 | }; 22 | 23 | { 24 | counter = 0; 25 | std::thread t1(funcWithoutLocks); 26 | std::thread t2(funcWithoutLocks); 27 | 28 | t1.join(); 29 | t2.join(); 30 | 31 | std::cout << "Counter without using locks: " << counter << std::endl; 32 | } 33 | { 34 | counter = 0; 35 | std::thread t1(funcWithLocks); 36 | std::thread t2(funcWithLocks); 37 | 38 | t1.join(); 39 | t2.join(); 40 | 41 | std::cout << "Counter using locks: " << counter << std::endl; 42 | } 43 | 44 | return 0; 45 | } -------------------------------------------------------------------------------- /Chapter_04/4x02-mutex.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | std::mutex mtx; 7 | uint32_t counter{}; 8 | 9 | void function_throws() { throw std::runtime_error("Error"); } 10 | 11 | int main() { 12 | auto worker = [] { 13 | for (int i = 0; i < 1000000; ++i) { 14 | mtx.lock(); 15 | counter++; 16 | mtx.unlock(); 17 | } 18 | }; 19 | 20 | auto worker_try = [] { 21 | int i = 0; 22 | while (i < 1000000) { 23 | if (mtx.try_lock()) { 24 | ++counter; 25 | ++i; 26 | mtx.unlock(); 27 | } 28 | } 29 | }; 30 | 31 | auto worker_exceptions = [] { 32 | for (int i = 0; i < 1000000; ++i) { 33 | try { 34 | std::lock_guard lock(mtx); 35 | counter++; 36 | function_throws(); 37 | } catch (std::system_error& e) { 38 | std::cout << e.what() << std::endl; 39 | return; 40 | } catch (...) { 41 | return; 42 | } 43 | } 44 | }; 45 | 46 | std::thread t1(worker); 47 | std::thread t2(worker_exceptions); 48 | std::thread t3(worker_try); 49 | 50 | t1.join(); 51 | t2.join(); 52 | t3.join(); 53 | 54 | std::cout << std::format("Final counter value: {0}\n", counter); 55 | 56 | //std::lock_guard lock1(); 57 | } -------------------------------------------------------------------------------- /Chapter_04/4x03-incorrect_mutex_lock.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main() { 7 | std::deque dq; 8 | 9 | dq.push_back(1); 10 | dq.push_back(2); 11 | dq.push_back(3); 12 | 13 | dq.pop_front(); 14 | 15 | std::cout << dq[0] << std::endl; 16 | 17 | std::mutex mtx; 18 | 19 | auto func = [&] { 20 | // double lock 21 | mtx.lock(); 22 | mtx.lock(); 23 | 24 | std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 25 | 26 | // double unlock 27 | mtx.unlock(); 28 | mtx.unlock(); 29 | 30 | // mutex destroyed with the thread still running and owning the lock 31 | }; 32 | 33 | std::thread t1(func); 34 | // t1.join(); 35 | return 0; 36 | } -------------------------------------------------------------------------------- /Chapter_04/4x04-mutex_queue.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "mutex_queue.h" 7 | 8 | void push_pop(std::size_t queue_size, int items, int producer_wait, int consumer_wait) { 9 | async_prog::queue queue(queue_size); 10 | 11 | std::jthread producer([&] { 12 | for (int i = 0; i < items; ++i) { 13 | queue.push(i); 14 | std::cout << std::format("Producer push {0}\n", i); 15 | 16 | std::this_thread::sleep_for(std::chrono::milliseconds(producer_wait)); 17 | } 18 | }); 19 | 20 | std::jthread consumer([&] { 21 | for (int i = 0; i < items; ++i) { 22 | int item{}; 23 | queue.pop(item); 24 | std::cout << std::format("Consumer pop {0}\n", item); 25 | 26 | std::this_thread::sleep_for(std::chrono::milliseconds(consumer_wait)); 27 | } 28 | }); 29 | } 30 | 31 | void try_push_try_pop(std::size_t queue_size, int items, int producer_wait, int consumer_wait) { 32 | async_prog::queue queue(queue_size); 33 | 34 | std::jthread producer([&] { 35 | int i{}; 36 | 37 | while (i < items) { 38 | if (queue.try_push(i)) { 39 | std::cout << std::format("Producer push {0}\n", i); 40 | std::this_thread::sleep_for(std::chrono::milliseconds(producer_wait)); 41 | 42 | ++i; 43 | } 44 | } 45 | }); 46 | 47 | std::jthread consumer([&] { 48 | int i{}; 49 | 50 | while (i < items) { 51 | int item{}; 52 | 53 | if (queue.try_pop(item)) { 54 | std::cout << std::format("Consumer pop {0}\n", item); 55 | std::this_thread::sleep_for(std::chrono::milliseconds(consumer_wait)); 56 | 57 | ++i; 58 | } 59 | } 60 | }); 61 | } 62 | 63 | int main() { 64 | push_pop(5, 1000, 1, 5); 65 | try_push_try_pop(5, 1000, 1, 5); 66 | 67 | return 0; 68 | } -------------------------------------------------------------------------------- /Chapter_04/4x05-call_once.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main() { 7 | std::once_flag run_once_flag; 8 | std::once_flag run_once_exceptions_flag; 9 | 10 | auto thread_function = [&] { 11 | std::call_once(run_once_flag, []{ 12 | std::cout << "This must run just once\n"; 13 | }); 14 | }; 15 | 16 | std::jthread t1(thread_function); 17 | std::jthread t2(thread_function); 18 | std::jthread t3(thread_function); 19 | 20 | auto function_throws = [&](bool throw_exception) { 21 | if (throw_exception) { 22 | std::cout << "Throwing exception\n"; 23 | throw std::runtime_error("runtime error"); 24 | } 25 | 26 | std::cout << "No exception was thrown\n"; 27 | }; 28 | 29 | auto thread_function_1 = [&](bool throw_exception) { 30 | try { 31 | std::call_once(run_once_exceptions_flag, function_throws, throw_exception); 32 | } catch (...) { 33 | } 34 | }; 35 | 36 | std::jthread t4(thread_function_1, true); 37 | std::jthread t5(thread_function_1, true); 38 | std::jthread t6(thread_function_1, false); 39 | 40 | return 0; 41 | } -------------------------------------------------------------------------------- /Chapter_04/4x06-semaphore_queue.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "semaphore_queue.h" 7 | 8 | void push_pop(std::size_t capacity, int items, int producer_wait, int consumer_wait) { 9 | async_prog::semaphore_queue queue(capacity); 10 | 11 | std::jthread t1([&] { 12 | for (int i = 0; i < items; ++i) { 13 | queue.push(i); 14 | std::cout << std::format("Produced item: {0}\n", i); 15 | 16 | std::this_thread::sleep_for(std::chrono::milliseconds(producer_wait)); 17 | } 18 | }); 19 | 20 | std::jthread t2([&] { 21 | for (int i = 0; i < items; ++i) { 22 | int item{}; 23 | queue.pop(item); 24 | std::cout << std::format("Consumed item: {0}\n", item); 25 | 26 | std::this_thread::sleep_for(std::chrono::milliseconds(consumer_wait)); 27 | } 28 | }); 29 | } 30 | 31 | void try_push_pop(std::size_t capacity, int items, int producer_wait, int consumer_wait) { 32 | async_prog::semaphore_queue queue(capacity); 33 | 34 | std::jthread t1([&] { 35 | int i{}; 36 | 37 | while (i < items) { 38 | if (!queue.try_push(i)) { 39 | std::this_thread::sleep_for(std::chrono::milliseconds(producer_wait)); 40 | } else { 41 | std::cout << std::format("Produced item: {0}\n", i); 42 | ++i; 43 | } 44 | } 45 | }); 46 | 47 | std::jthread t2([&] { 48 | int i{}; 49 | while (i < items) { 50 | int item{}; 51 | if (!queue.try_pop(item)) { 52 | std::this_thread::sleep_for(std::chrono::milliseconds(consumer_wait)); 53 | } else { 54 | std::cout << std::format("Consumed item: {0}\n", item); 55 | ++i; 56 | } 57 | } 58 | }); 59 | } 60 | 61 | int main() { 62 | auto pre = std::chrono::high_resolution_clock::now(); 63 | push_pop(5, 1000, 1, 1); 64 | auto post = std::chrono::high_resolution_clock::now(); 65 | 66 | auto millisec = std::chrono::duration_cast(post - pre); 67 | std::cout << std::format("push/pop took {0} to process {1} items\n", millisec, 1000); 68 | 69 | pre = std::chrono::high_resolution_clock::now(); 70 | try_push_pop(5, 1000, 1, 1); 71 | post = std::chrono::high_resolution_clock::now(); 72 | 73 | millisec = std::chrono::duration_cast(post - pre); 74 | std::cout << std::format("try_push/try_pop took {0} to process {1} items\n", millisec, 1000); 75 | 76 | return 0; 77 | } -------------------------------------------------------------------------------- /Chapter_04/4x07-latches_and_barriers.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | void multiply_add_latch() { 11 | const int NUM_THREADS{3}; 12 | 13 | std::latch map_latch{NUM_THREADS}; 14 | std::latch reduce_latch{1}; 15 | 16 | std::vector numbers(3000); 17 | int sum{}; 18 | std::iota(numbers.begin(), numbers.end(), 0); 19 | 20 | auto map_thread = [&](std::vector& numbers, int start, int end) { 21 | for (int i = start; i < end; ++i) { 22 | numbers[i] *= 2; 23 | } 24 | 25 | map_latch.count_down(); 26 | }; 27 | 28 | auto reduce_thread = [&](const std::vector& numbers, int& sum) { 29 | map_latch.wait(); 30 | 31 | sum = std::accumulate(numbers.begin(), numbers.end(), 0); 32 | 33 | reduce_latch.count_down(); 34 | }; 35 | 36 | for (int i = 0; i < NUM_THREADS; ++i) { 37 | std::jthread t(map_thread, std::ref(numbers), 1000 * i, 1000 * (i + 1)); 38 | } 39 | 40 | std::jthread t(reduce_thread, numbers, std::ref(sum)); 41 | 42 | reduce_latch.wait(); 43 | 44 | std::cout << "All threads finished. The total sum is: " << sum << '\n'; 45 | } 46 | 47 | void multiply_add_barrier() { 48 | const int NUM_THREADS{3}; 49 | 50 | std::vector sum(3, 0); 51 | std::vector numbers(3000); 52 | std::iota(numbers.begin(), numbers.end(), 0); 53 | 54 | std::barrier map_barrier{NUM_THREADS}; 55 | 56 | auto worker_thread = [&](std::vector& numbers, int start, int end, int id) { 57 | std::cout << std::format("Thread {0} is starting...\n", id); 58 | 59 | for (int i = start; i < end; ++i) { 60 | numbers[i] *= 2; 61 | } 62 | 63 | map_barrier.arrive_and_wait(); 64 | 65 | for (int i = start; i < end; ++i) { 66 | sum[id] += numbers[i]; 67 | } 68 | 69 | auto tk = map_barrier.arrive(); 70 | (void) tk; 71 | }; 72 | 73 | std::vector workers; 74 | for (int i = 0; i < NUM_THREADS; ++i) { 75 | workers.emplace_back(worker_thread, std::ref(numbers), 1000 * i, 1000 * (i + 1), i); 76 | } 77 | 78 | for (auto& t : workers) { 79 | t.join(); 80 | } 81 | 82 | std::cout << std::format("All threads finished. The total sum is: {0}\n", 83 | std::accumulate(sum.begin(), sum.end(), 0)); 84 | } 85 | 86 | int main() { 87 | std::cout << "Multiplying and reducing vector using barriers..." << std::endl; 88 | multiply_add_barrier(); 89 | 90 | std::cout << "Multiplying and reducing vector using latches..." << std::endl; 91 | multiply_add_latch(); 92 | return 0; 93 | } 94 | -------------------------------------------------------------------------------- /Chapter_04/4x08-shared_mutex.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int counter = 0; 8 | 9 | int main() 10 | { 11 | using namespace std::chrono_literals; 12 | 13 | std::shared_mutex mutex; 14 | 15 | auto reader = [&] { 16 | for (int i = 0; i < 10; ++i) { 17 | mutex.lock_shared(); 18 | // Read the counter and do something 19 | mutex.unlock_shared(); 20 | } 21 | }; 22 | 23 | auto writer = [&] { 24 | for (int i = 0; i < 10; ++i) { 25 | mutex.lock(); 26 | ++counter; 27 | std::cout << "Counter: " << counter << std::endl; 28 | mutex.unlock(); 29 | 30 | std::this_thread::sleep_for(10ms); 31 | } 32 | }; 33 | 34 | std::thread t1(reader); 35 | std::thread t2(reader); 36 | std::thread t3(writer); 37 | std::thread t4(reader); 38 | std::thread t5(reader); 39 | std::thread t6(writer); 40 | 41 | t1.join(); 42 | t2.join(); 43 | t3.join(); 44 | t4.join(); 45 | t5.join(); 46 | t6.join(); 47 | 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /Chapter_04/4x09-try_lock_for.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | constexpr int NUM_THREADS = 8; 9 | int counter = 0; 10 | int failed = 0; 11 | 12 | int main() 13 | { 14 | using namespace std::chrono_literals; 15 | 16 | std::timed_mutex mutex; 17 | std::mutex m; 18 | 19 | auto worker = [&] { 20 | for (int i = 0; i < 10; ++i) { 21 | if (mutex.try_lock_for(10ms)) { 22 | ++counter; 23 | std::cout << "Counter: " << counter << std::endl; 24 | std::this_thread::sleep_for(10ms); 25 | mutex.unlock(); 26 | } 27 | else { 28 | m.lock(); 29 | ++failed; 30 | std::cout << "Thread " << std::this_thread::get_id() << " failed to lock" << std::endl; 31 | m.unlock(); 32 | } 33 | 34 | std::this_thread::sleep_for(12ms); 35 | } 36 | }; 37 | 38 | std::vector threads; 39 | for (int i = 0; i < NUM_THREADS; ++i) { 40 | threads.emplace_back(worker); 41 | } 42 | 43 | for (auto& t : threads) { 44 | t.join(); 45 | } 46 | 47 | std::cout << "Counter: " << counter << std::endl; 48 | std::cout << "Failed: " << failed << std::endl; 49 | 50 | return 0; 51 | } 52 | -------------------------------------------------------------------------------- /Chapter_04/4x10-condition_variable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int counter = 0; 9 | 10 | int main() 11 | { 12 | using namespace std::chrono_literals; 13 | 14 | std::mutex mtx; 15 | std::mutex cout_mtx; 16 | std::condition_variable cv; 17 | 18 | auto increment_counter = [&] { 19 | for (int i = 0; i < 20; ++i) { 20 | std::this_thread::sleep_for(100ms); 21 | mtx.lock(); 22 | ++counter; 23 | mtx.unlock(); 24 | cv.notify_one(); 25 | } 26 | }; 27 | 28 | auto wait_for_counter_non_zero_mtx = [&] { 29 | mtx.lock(); 30 | while (counter == 0) { 31 | mtx.unlock(); 32 | std::this_thread::sleep_for(10ms); 33 | mtx.lock(); 34 | } 35 | mtx.unlock(); 36 | std::lock_guard cout_lck(cout_mtx); 37 | std::cout << "Counter is non-zero" << std::endl; 38 | }; 39 | 40 | auto wait_for_counter_10_cv = [&] { 41 | std::unique_lock lck(mtx); 42 | cv.wait(lck, [] { return counter == 10; }); 43 | 44 | std::lock_guard cout_lck(cout_mtx); 45 | std::cout << "Counter is: " << counter << std::endl; 46 | }; 47 | 48 | std::thread t1(wait_for_counter_non_zero_mtx); 49 | std::thread t2(wait_for_counter_10_cv); 50 | std::thread t3(increment_counter); 51 | 52 | t1.join(); 53 | t2.join(); 54 | t3.join(); 55 | 56 | return 0; 57 | } 58 | 59 | -------------------------------------------------------------------------------- /Chapter_04/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Output directory 2 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BINARY_DIR}/Chapter_04) 3 | 4 | # Create executable for each cpp file 5 | file(GLOB SOURCES "*.cpp") 6 | foreach(SOURCE ${SOURCES}) 7 | get_filename_component(EXECUTABLE_NAME ${SOURCE} NAME_WE) 8 | add_executable(${EXECUTABLE_NAME} ${SOURCE}) 9 | target_link_libraries(${EXECUTABLE_NAME} ${Boost_LIBRARIES}) 10 | endforeach() 11 | -------------------------------------------------------------------------------- /Chapter_04/mutex_queue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace async_prog { 8 | 9 | template 10 | class queue { 11 | public: 12 | queue(std::size_t capacity) : capacity_{capacity}, buffer_(capacity) {} 13 | 14 | void push(const T& item) { 15 | std::unique_lock lock(mtx_); 16 | not_full_.wait(lock, [this] { return !is_full(); }); 17 | 18 | buffer_[tail_] = item; 19 | tail_ = next(tail_); 20 | 21 | lock.unlock(); 22 | 23 | not_empty_.notify_one(); 24 | } 25 | 26 | bool try_push(const T& item) { 27 | std::unique_lock lock(mtx_, std::try_to_lock); 28 | if (!lock || is_full()) { 29 | return false; 30 | } 31 | 32 | buffer_[tail_] = item; 33 | tail_ = next(tail_); 34 | 35 | lock.unlock(); 36 | 37 | not_empty_.notify_one(); 38 | 39 | return true; 40 | } 41 | 42 | void pop(T& item) { 43 | std::unique_lock lock(mtx_); 44 | not_empty_.wait(lock, [this] { return !is_empty(); }); 45 | 46 | item = buffer_[head_]; 47 | head_ = next(head_); 48 | 49 | lock.unlock(); 50 | 51 | not_full_.notify_one(); 52 | } 53 | 54 | bool try_pop(T& item) { 55 | std::unique_lock lock(mtx_, std::try_to_lock); 56 | if (!lock || is_empty()) { 57 | return false; 58 | } 59 | 60 | item = buffer_[head_]; 61 | head_ = next(head_); 62 | 63 | lock.unlock(); 64 | 65 | not_empty_.notify_one(); 66 | 67 | return true; 68 | } 69 | 70 | private: 71 | [[nodiscard]] std::size_t next(std::size_t idx) const noexcept { return ((idx + 1) % capacity_); } 72 | 73 | [[nodiscard]] bool is_empty() const noexcept { return (head_ == tail_); } 74 | 75 | [[nodiscard]] bool is_full() const noexcept { return (next(tail_) == head_); } 76 | 77 | private: 78 | std::mutex mtx_; 79 | std::condition_variable not_empty_; 80 | std::condition_variable not_full_; 81 | 82 | std::size_t head_{0}; 83 | std::size_t tail_{0}; 84 | std::size_t capacity_; 85 | std::vector buffer_; 86 | }; 87 | 88 | } // namespace async_prog 89 | -------------------------------------------------------------------------------- /Chapter_04/semaphore_queue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace async_prog { 8 | 9 | template 10 | class semaphore_queue { 11 | public: 12 | semaphore_queue(std::size_t capacity) 13 | : sem_empty_(capacity), sem_full_(0), capacity_{capacity}, buffer_(capacity) {} 14 | 15 | void push(const T& item) { 16 | sem_empty_.acquire(); 17 | 18 | std::unique_lock lock(mtx_); 19 | 20 | buffer_[tail_] = item; 21 | tail_ = next(tail_); 22 | 23 | lock.unlock(); 24 | 25 | sem_full_.release(); 26 | } 27 | 28 | bool try_push(const T& item) { 29 | if (!sem_empty_.try_acquire()) { 30 | return false; 31 | } 32 | 33 | std::unique_lock lock(mtx_); 34 | 35 | buffer_[tail_] = item; 36 | tail_ = next(tail_); 37 | 38 | lock.unlock(); 39 | 40 | sem_full_.release(); 41 | 42 | return true; 43 | } 44 | 45 | void pop(T& item) { 46 | sem_full_.acquire(); 47 | 48 | std::unique_lock lock(mtx_); 49 | 50 | item = buffer_[head_]; 51 | head_ = next(head_); 52 | 53 | lock.unlock(); 54 | 55 | sem_empty_.release(); 56 | } 57 | 58 | bool try_pop(T& item) { 59 | if (!sem_full_.try_acquire()) { 60 | return false; 61 | } 62 | 63 | std::unique_lock lock(mtx_); 64 | 65 | item = buffer_[head_]; 66 | head_ = next(head_); 67 | 68 | lock.unlock(); 69 | 70 | sem_empty_.release(); 71 | 72 | return true; 73 | } 74 | 75 | private: 76 | [[nodiscard]] std::size_t next(std::size_t idx) const noexcept { return ((idx + 1) % capacity_); } 77 | 78 | private: 79 | std::mutex mtx_; 80 | std::counting_semaphore<> sem_empty_; 81 | std::counting_semaphore<> sem_full_; 82 | 83 | std::size_t head_{0}; 84 | std::size_t tail_{0}; 85 | std::size_t capacity_; 86 | std::vector buffer_; 87 | }; 88 | 89 | } // namespace async_prog 90 | -------------------------------------------------------------------------------- /Chapter_05/5x01-atomic_flag.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | class spin_lock { 8 | public: 9 | spin_lock() = default; 10 | 11 | spin_lock(const spin_lock &) = delete; 12 | 13 | spin_lock &operator=(const spin_lock &) = delete; 14 | 15 | void lock() { 16 | while (flag.test_and_set(std::memory_order_acquire)) { 17 | } 18 | } 19 | 20 | void unlock() { 21 | flag.clear(std::memory_order_release); 22 | } 23 | 24 | private: 25 | std::atomic_flag flag = ATOMIC_FLAG_INIT; 26 | }; 27 | 28 | constexpr uint32_t NUM_THREADS{4}; 29 | uint32_t counter{0}; 30 | std::atomic atomic_counter{0}; 31 | 32 | int main() { 33 | std::mutex m; 34 | auto start = std::chrono::high_resolution_clock::now(); 35 | std::vector threads; 36 | for (uint32_t i = 0; i < NUM_THREADS; ++i) { 37 | threads.emplace_back([&m] { 38 | for (int i = 0; i < 200000000; ++i) { 39 | m.lock(); 40 | counter++; 41 | m.unlock(); 42 | } 43 | }); 44 | } 45 | for (auto &t: threads) { 46 | t.join(); 47 | } 48 | auto stop = std::chrono::high_resolution_clock::now(); 49 | std::chrono::duration elapsed = stop - start; 50 | std::cout << "Elapsed time: " << elapsed.count() << std::endl; 51 | std::cout << counter << std::endl; 52 | 53 | spin_lock sl; 54 | counter = 0; 55 | start = std::chrono::high_resolution_clock::now(); 56 | for (uint32_t i = 0; i < NUM_THREADS; ++i) { 57 | threads[i] = std::thread([&sl] { 58 | for (int i = 0; i < 500000000; ++i) { 59 | sl.lock(); 60 | counter++; 61 | sl.unlock(); 62 | } 63 | }); 64 | } 65 | 66 | for (auto &t: threads) { 67 | t.join(); 68 | } 69 | stop = std::chrono::high_resolution_clock::now(); 70 | elapsed = stop - start; 71 | std::cout << "Elapsed time: " << elapsed.count() << std::endl; 72 | std::cout << counter << std::endl; 73 | 74 | start = std::chrono::high_resolution_clock::now(); 75 | for (uint32_t i = 0; i < NUM_THREADS; ++i) { 76 | threads[i] = std::thread([]() { 77 | for (int i = 0; i < 500000000; ++i) { 78 | atomic_counter.fetch_add(1, std::memory_order_relaxed); 79 | } 80 | }); 81 | } 82 | for (auto &t: threads) { 83 | t.join(); 84 | } 85 | stop = std::chrono::high_resolution_clock::now(); 86 | elapsed = stop - start; 87 | std::cout << "Elapsed time: " << elapsed.count() << std::endl; 88 | std::cout << atomic_counter << std::endl; 89 | 90 | return 0; 91 | } -------------------------------------------------------------------------------- /Chapter_05/5x02-thread_progress.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | constexpr int NUM_ITEMS{100000}; 7 | 8 | int main() { 9 | using namespace std::chrono_literals; 10 | 11 | std::atomic progress{0}; 12 | 13 | std::thread worker([&progress] { 14 | for (int i = 1; i <= NUM_ITEMS; ++i) { 15 | progress.store(i, std::memory_order_relaxed); 16 | std::this_thread::sleep_for(1ms); 17 | } 18 | }); 19 | 20 | while (true) { 21 | int processed_items = progress.load(std::memory_order_relaxed); 22 | std::cout << "Progress: " << processed_items << " / " << NUM_ITEMS << std::endl; 23 | if (processed_items == NUM_ITEMS) { 24 | break; 25 | } 26 | std::this_thread::sleep_for(10s); 27 | } 28 | 29 | worker.join(); 30 | 31 | return 0; 32 | } -------------------------------------------------------------------------------- /Chapter_05/5x03-simple-statistics.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | constexpr int NUM_ITEMS{10000}; 8 | 9 | void process() { 10 | std::random_device rd; 11 | std::mt19937 gen(rd()); 12 | std::uniform_int_distribution<> dis(1, 20); 13 | 14 | int sleep_duration = dis(gen); 15 | 16 | std::this_thread::sleep_for(std::chrono::milliseconds(sleep_duration)); 17 | } 18 | 19 | int main() { 20 | std::atomic processed_items{0}; 21 | std::atomic total_time{0.0f}; 22 | std::atomic average_time{0.0}; 23 | 24 | std::cout << "std::atomic is lock-free: " << std::boolalpha << processed_items.is_lock_free() << std::endl; 25 | std::cout << "std::atomic is always lock-free: " << std::boolalpha << std::atomic::is_always_lock_free 26 | << std::endl; 27 | std::cout << "std::atomic is lock-free: " << std::boolalpha << total_time.is_lock_free() << std::endl; 28 | std::cout << "std::atomic is always lock-free: " << std::boolalpha << std::atomic::is_always_lock_free 29 | << std::endl; 30 | std::cout << "std::atomic is lock-free: " << std::boolalpha << average_time.is_lock_free() << std::endl; 31 | std::cout << "std::atomic is always lock-free: " << std::boolalpha 32 | << std::atomic::is_always_lock_free << std::endl; 33 | 34 | std::thread worker([&] { 35 | for (int i = 1; i <= NUM_ITEMS; ++i) { 36 | auto now = std::chrono::high_resolution_clock::now(); 37 | process(); 38 | auto elapsed = std::chrono::high_resolution_clock::now() - now; 39 | float elapsed_s = std::chrono::duration(elapsed).count(); 40 | 41 | processed_items.fetch_add(1, std::memory_order_relaxed); 42 | total_time.fetch_add(elapsed_s, std::memory_order_relaxed); 43 | average_time.store(total_time.load() / processed_items.load(), std::memory_order_relaxed); 44 | } 45 | }); 46 | 47 | while (true) { 48 | int items = processed_items.load(std::memory_order_relaxed); 49 | std::cout << "Progress: " << items << " / " << NUM_ITEMS << std::endl; 50 | 51 | float time = total_time.load(std::memory_order_relaxed); 52 | std::cout << "Total time: " << time << " sec" << std::endl; 53 | 54 | double average = average_time.load(std::memory_order_relaxed); 55 | std::cout << "Average time: " << average * 1000 << " ms" << std::endl; 56 | 57 | if (items == NUM_ITEMS) { 58 | break; 59 | } 60 | 61 | std::this_thread::sleep_for(std::chrono::seconds(5)); 62 | } 63 | 64 | worker.join(); 65 | 66 | return 0; 67 | } 68 | -------------------------------------------------------------------------------- /Chapter_05/5x04-atomic_type_not_lock-free.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct no_lock_free { 5 | int a[128]; 6 | 7 | no_lock_free() { 8 | for (int i = 0; i < 128; ++i) { 9 | a[i] = i; 10 | } 11 | } 12 | }; 13 | 14 | int main() { 15 | std::atomic s; 16 | 17 | std::cout << "Size of no_lock_free: " << sizeof(no_lock_free) << " bytes\n"; 18 | std::cout << "Size of std::atomic: " << sizeof(s) << " bytes\n"; 19 | 20 | std::cout << "Is std::atomic always lock-free: " << std::boolalpha 21 | << std::atomic::is_always_lock_free << std::endl; 22 | std::cout << "Is std::atomic lock-free: " << std::boolalpha << s.is_lock_free() << std::endl; 23 | 24 | no_lock_free s1; 25 | s.store(s1); 26 | 27 | return 0; 28 | } -------------------------------------------------------------------------------- /Chapter_05/5x05-lazy-on-time-initialization.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | constexpr int NUM_THREADS{8}; 8 | 9 | void process() { 10 | std::random_device rd; 11 | std::mt19937 gen(rd()); 12 | std::uniform_int_distribution<> dis(1, 1000000); 13 | 14 | int sleep_duration = dis(gen); 15 | 16 | std::this_thread::sleep_for(std::chrono::microseconds(sleep_duration)); 17 | } 18 | 19 | int main() { 20 | std::atomic init_thread{0}; 21 | 22 | auto worker = [&init_thread](int i) { 23 | process(); 24 | 25 | int init_value = init_thread.load(std::memory_order::seq_cst); 26 | if (init_value == 0) { 27 | int expected = 0; 28 | if (init_thread.compare_exchange_strong(expected, i, std::memory_order::seq_cst)) { 29 | std::cout << "Previous value of init_thread: " << expected << "\n"; 30 | std::cout << "Thread " << i << " initialized\n"; 31 | } else { 32 | // init_thread was already initialized 33 | } 34 | } else { 35 | // init_thread was already initialized 36 | } 37 | }; 38 | 39 | 40 | std::vector threads; 41 | for (int i = 1; i <= NUM_THREADS; ++i) { 42 | threads.emplace_back(worker, i); 43 | } 44 | 45 | for (auto &t: threads) { 46 | t.join(); 47 | } 48 | 49 | std::cout << "Thread: " << init_thread.load() << " initialized\n"; 50 | 51 | return 0; 52 | } -------------------------------------------------------------------------------- /Chapter_05/5x06-atomics_synchronization.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | std::string message; 8 | std::atomic ready{false}; 9 | 10 | void reader() { 11 | using namespace std::chrono_literals; 12 | 13 | while (!ready.load()) { 14 | std::this_thread::sleep_for(1ms); 15 | } 16 | 17 | std::cout << "Message received = " << message << std::endl; 18 | } 19 | 20 | void writer() { 21 | message = "Hello, World!"; 22 | ready.store(true); 23 | } 24 | 25 | int main() { 26 | std::thread t1(reader); 27 | std::thread t2(writer); 28 | 29 | t1.join(); 30 | t2.join(); 31 | 32 | return 0; 33 | } -------------------------------------------------------------------------------- /Chapter_05/5x07-sequential_consistency.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | std::atomic x{false}; 7 | std::atomic y{false}; 8 | std::atomic z{0}; 9 | 10 | void write_x() { 11 | x.store(true, std::memory_order_seq_cst); 12 | } 13 | 14 | void write_y() { 15 | y.store(true, std::memory_order_seq_cst); 16 | } 17 | 18 | void read_x_then_y() { 19 | while (!x.load(std::memory_order_seq_cst)) { 20 | 21 | } 22 | 23 | if (y.load(std::memory_order_seq_cst)) { 24 | ++z; 25 | } 26 | } 27 | 28 | void read_y_then_x() { 29 | while (!y.load(std::memory_order_seq_cst)) { 30 | } 31 | 32 | if (x.load(std::memory_order_seq_cst)) { 33 | ++z; 34 | } 35 | } 36 | 37 | int main() { 38 | std::thread t1(write_x); 39 | std::thread t2(write_y); 40 | std::thread t3(read_x_then_y); 41 | std::thread t4(read_y_then_x); 42 | 43 | t1.join(); 44 | t2.join(); 45 | t3.join(); 46 | t4.join(); 47 | 48 | if (z.load() == 0) { 49 | std::cout << "This will never happen\n"; 50 | } 51 | { 52 | std::cout << "This will always happen and z = " << z << "\n"; 53 | } 54 | 55 | return 0; 56 | } -------------------------------------------------------------------------------- /Chapter_05/5x08-acquire_release.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | std::atomic x{false}; 8 | std::atomic y{false}; 9 | std::atomic z{0}; 10 | 11 | std::atomic msg{nullptr}; 12 | int data{0}; 13 | 14 | 15 | void producer() { 16 | auto *p = new std::string("Hello"); 17 | msg.store(p, std::memory_order_release); 18 | data = 42; 19 | } 20 | 21 | void consumer() { 22 | std::string *p2; 23 | while (!(p2 = msg.load(std::memory_order_acquire))) { 24 | } 25 | 26 | std::cout << *p2 << std::endl; 27 | std::cout << "The answer is " << data << std::endl; 28 | } 29 | 30 | void write_x() { 31 | x.store(true, std::memory_order_release); 32 | } 33 | 34 | void write_y() { 35 | y.store(true, std::memory_order_release); 36 | } 37 | 38 | void read_x_then_y() { 39 | while (!x.load(std::memory_order_acquire)) { 40 | 41 | } 42 | 43 | if (y.load(std::memory_order_acquire)) { 44 | ++z; 45 | } 46 | } 47 | 48 | void read_y_then_x() { 49 | while (!y.load(std::memory_order_acquire)) { 50 | } 51 | 52 | if (x.load(std::memory_order_acquire)) { 53 | ++z; 54 | } 55 | } 56 | 57 | int main() { 58 | std::thread t1(producer); 59 | std::thread t2(consumer); 60 | t1.join(); 61 | t2.join(); 62 | 63 | for (int i = 0; i < 10; ++i) { 64 | std::thread t1(write_x); 65 | std::thread t2(write_y); 66 | std::thread t3(read_x_then_y); 67 | std::thread t4(read_y_then_x); 68 | 69 | t1.join(); 70 | t2.join(); 71 | t3.join(); 72 | t4.join(); 73 | 74 | if (z.load() == 0) { 75 | std::cout << "This will never happen\n"; 76 | } 77 | { 78 | std::cout << "This will always happen and z = " << z << "\n"; 79 | } 80 | } 81 | 82 | return 0; 83 | } -------------------------------------------------------------------------------- /Chapter_05/5x09-SPSC_lock_free_queue.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | template 9 | class spsc_lock_free_queue { 10 | public: 11 | // capacity must be power of two to avoid using modulo operator when calculating the index 12 | explicit spsc_lock_free_queue(size_t capacity) : capacity_(capacity), buffer_(capacity) { 13 | assert((capacity & (capacity - 1)) == 0 && "capacity must be a power of 2"); 14 | } 15 | 16 | spsc_lock_free_queue(const spsc_lock_free_queue &) = delete; 17 | 18 | spsc_lock_free_queue &operator=(const spsc_lock_free_queue &) = delete; 19 | 20 | bool push(const T &item) { 21 | std::size_t tail = tail_.load(std::memory_order_relaxed); 22 | std::size_t next_tail = (tail + 1) & (capacity_ - 1); 23 | if (next_tail != head_.load(std::memory_order_acquire)) { 24 | buffer_[tail] = item; 25 | tail_.store(next_tail, std::memory_order_release); 26 | return true; 27 | } 28 | 29 | return false; 30 | } 31 | 32 | bool pop(T &item) { 33 | std::size_t head = head_.load(std::memory_order_relaxed); 34 | if (head == tail_.load(std::memory_order_acquire)) { 35 | return false; 36 | } 37 | 38 | item = buffer_[head]; 39 | head_.store((head + 1) & (capacity_ - 1), std::memory_order_release); 40 | 41 | return true; 42 | } 43 | 44 | private: 45 | const std::size_t capacity_; 46 | std::vector buffer_; 47 | std::atomic head_{0}; 48 | std::atomic tail_{0}; 49 | }; 50 | 51 | void producer(spsc_lock_free_queue &queue) { 52 | for (int i = 0; i < 1000; ++i) { 53 | while (!queue.push(i)) { 54 | } 55 | } 56 | } 57 | 58 | void consumer(spsc_lock_free_queue &queue) { 59 | for (int i = 0; i < 1000; ++i) { 60 | int data; 61 | while (!queue.pop(data)) { 62 | } 63 | std::cout << data << std::endl; 64 | } 65 | } 66 | 67 | 68 | int main() { 69 | std::atomic test{0}; 70 | std::cout << "std::atomic is lock-free: " << std::boolalpha << test.is_lock_free() << std::endl; 71 | std::cout << "std::atomic is always lock-free: " << std::boolalpha 72 | << std::atomic::is_always_lock_free << std::endl; 73 | 74 | spsc_lock_free_queue queue(8); 75 | 76 | std::thread t1(producer, std::ref(queue)); 77 | std::thread t2(consumer, std::ref(queue)); 78 | 79 | t1.join(); 80 | t2.join(); 81 | 82 | return 0; 83 | } -------------------------------------------------------------------------------- /Chapter_05/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Output directory 2 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BINARY_DIR}/Chapter_05) 3 | 4 | # Create executable for each cpp file 5 | file(GLOB SOURCES "*.cpp") 6 | foreach(SOURCE ${SOURCES}) 7 | get_filename_component(EXECUTABLE_NAME ${SOURCE} NAME_WE) 8 | add_executable(${EXECUTABLE_NAME} ${SOURCE}) 9 | target_link_libraries(${EXECUTABLE_NAME} ${Boost_LIBRARIES} atomic) 10 | endforeach() 11 | -------------------------------------------------------------------------------- /Chapter_06/6x01-promise_set_value_param.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() { 6 | std::promise prom; 7 | std::future fut = prom.get_future(); 8 | 9 | auto threadFunc = [](std::promise prom) { 10 | try { 11 | int result = 42; 12 | prom.set_value(result); 13 | } catch (...) { 14 | prom.set_exception(std::current_exception()); 15 | } 16 | }; 17 | 18 | std::jthread t(threadFunc, std::move(prom)); 19 | 20 | try { 21 | int result = fut.get(); 22 | std::cout << "Result from thread: " << result << std::endl; 23 | } catch (const std::exception& e) { 24 | std::cerr << "Exception: " << e.what() << std::endl; 25 | } 26 | 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /Chapter_06/6x02-promise_set_value_lambda.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std::literals; 7 | 8 | int main() { 9 | std::promise prom; 10 | std::future fut = prom.get_future(); 11 | 12 | auto t1 = std::jthread([prm = std::move(prom)] mutable { 13 | std::this_thread::sleep_for(100ms); 14 | prm.set_value("Value successfully set."); 15 | // We could also use: prm.set_value_at_thread_exit("Value successfully set."s); 16 | }); 17 | 18 | std::cout << fut.get() << std::endl; 19 | 20 | std::promise other_prom; 21 | std::future other_fut = other_prom.get_future(); 22 | auto t2 = std::jthread([prm = std::move(other_prom)] mutable { 23 | try { 24 | throw std::runtime_error("Throwing internal exception."); 25 | prm.set_value(1); 26 | } catch (...) { 27 | prm.set_exception(std::current_exception()); 28 | // We could also use: prm.set_exception_at_thread_exit(std::current_exception()); 29 | } 30 | }); 31 | 32 | try { 33 | auto val = other_fut.get(); 34 | std::cout << "This will not be printed: " << val << '\n'; 35 | } catch (const std::exception& e) { 36 | std::cout << "Propagated exception: " << e.what() << '\n'; 37 | } 38 | 39 | return 0; 40 | } -------------------------------------------------------------------------------- /Chapter_06/6x03-promise_set_value_as_barrier.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std::chrono_literals; 13 | 14 | int main() { 15 | std::istringstream iss_numbers{"10 5 2 6 4 1 3 9 7 8"}; 16 | std::istringstream iss_letters{"A b 53 C,d 83D 4B ca"}; 17 | 18 | std::vector numbers; 19 | std::set letters; 20 | 21 | std::promise numbers_promise, letters_promise; 22 | auto numbers_ready = numbers_promise.get_future(); 23 | auto letter_ready = letters_promise.get_future(); 24 | 25 | std::jthread input_data_thread([&] { 26 | // Step 1: Emulating an I/O operations by copying data into vector numbers. 27 | std::copy(std::istream_iterator{iss_numbers}, 28 | std::istream_iterator{}, 29 | std::back_inserter(numbers)); 30 | 31 | // Notify completion of Step 1. 32 | numbers_promise.set_value(); 33 | 34 | // Step 2: Emulating further I/O operations. 35 | std::copy_if(std::istreambuf_iterator{iss_letters}, 36 | std::istreambuf_iterator{}, 37 | std::inserter(letters, letters.end()), ::isalpha); 38 | 39 | // Notify completion of Step 2. 40 | letters_promise.set_value(); 41 | }); 42 | 43 | // Wait for numbers vector to be filled. 44 | numbers_ready.wait(); 45 | 46 | // Sort numbers. 47 | std::sort(numbers.begin(), numbers.end()); 48 | 49 | // If letters vector still not ready, print numbers. 50 | if (letter_ready.wait_for(1s) == std::future_status::timeout) { 51 | for (int num : numbers) std::cout << num << ' '; 52 | numbers.clear(); 53 | } 54 | 55 | // Wait for letters vector to be filled. 56 | letter_ready.wait(); 57 | 58 | // Print numbers if not yet printed, otherwise vector is empty (cleared). 59 | for (int num : numbers) std::cout << num << ' '; 60 | std::cout << std::endl; 61 | 62 | for (char let : letters) std::cout << let << ' '; 63 | std::cout << std::endl; 64 | } 65 | 66 | 67 | -------------------------------------------------------------------------------- /Chapter_06/6x04-empty_future_get_throws.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | std::future empty; 6 | try { 7 | int n = empty.get(); 8 | std::cout << "n = " << n << std::endl; 9 | } catch (const std::future_error& e) { 10 | std::cerr << "Caught a future_error exception [ " << e.code() << " ] : " << e.what() << std::endl; 11 | } 12 | } -------------------------------------------------------------------------------- /Chapter_06/6x05-shared_future.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define sync_cout std::osyncstream(std::cout) 8 | 9 | int main() { 10 | std::promise prom; 11 | std::future fut = prom.get_future(); 12 | std::shared_future shared_fut = fut.share(); 13 | 14 | std::vector threads; 15 | for (int i = 1; i <= 5; ++i) { 16 | // Each thread will get the result of the shared_future. 17 | threads.emplace_back( 18 | [shared_fut, i]() { sync_cout << "Thread " << i << ": Result = " << shared_fut.get() << std::endl; }); 19 | } 20 | 21 | // At this point, all threads are running and waiting for the result. 22 | // Here we send the same result to all of them. 23 | prom.set_value(5); 24 | 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /Chapter_06/6x06-packaged_tasks.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | void task_thread() { 8 | std::packaged_task task(std::pow); 9 | std::future result = task.get_future(); 10 | std::jthread t(std::move(task), 2, 10); 11 | std::cout << "Task thread: " << result.get() << '\n'; 12 | } 13 | 14 | void task_lambda() { 15 | std::packaged_task task([](int a, int b) { return std::pow(a, b); }); 16 | std::future result = task.get_future(); 17 | task(2, 10); 18 | std::cout << "Task lambda: " << result.get() << '\n'; 19 | } 20 | 21 | void task_bind() { 22 | std::packaged_task task(std::bind(std::pow, 2, 10)); 23 | std::future result = task.get_future(); 24 | task(); 25 | std::cout << "Task bind: " << result.get() << '\n'; 26 | } 27 | 28 | int main() { 29 | task_thread(); 30 | task_lambda(); 31 | task_bind(); 32 | } -------------------------------------------------------------------------------- /Chapter_06/6x07-packaged_tasks_ready_at_exit.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std::chrono_literals; 9 | 10 | void task_func(std::future& output) { 11 | std::packaged_task task{[](bool& done) { done = true; }}; 12 | auto result = task.get_future(); 13 | 14 | bool done = false; 15 | task.make_ready_at_thread_exit(done); 16 | 17 | std::cout << "task_func: done = " << std::boolalpha << done << std::endl; 18 | 19 | auto status = result.wait_for(0s); 20 | if (status == std::future_status::timeout) 21 | std::cout << "task_func: result not ready" << std::endl; 22 | 23 | output = std::move(result); 24 | } 25 | 26 | int main() { 27 | std::future result; 28 | 29 | std::thread t{task_func, std::ref(result)}; 30 | t.join(); 31 | 32 | auto status = result.wait_for(0s); 33 | if (status == std::future_status::ready) 34 | std::cout << "main: result ready" << std::endl; 35 | return 0; 36 | } -------------------------------------------------------------------------------- /Chapter_06/6x08-packaged_tasks_reset.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main() { 7 | // Reusing a packaged task by resetting it 8 | std::packaged_task task([](int a, int b) { return std::pow(a, b); }); 9 | 10 | for (int i=1; i<=10; ++i) { 11 | std::future result = task.get_future(); 12 | task(2, i); 13 | std::cout << "2^" << i << " = " << result.get() << std::endl; 14 | task.reset(); 15 | } 16 | 17 | return 0; 18 | } -------------------------------------------------------------------------------- /Chapter_06/6x09-cancelling-tasks.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std::chrono_literals; 7 | 8 | const int CHECK_PERIOD_MS = 100; // Cancellation token checking period (ms) 9 | 10 | bool long_running_task(int ms, const std::atomic_bool& cancellation_token) { 11 | // Run the task for an amount of milliseconds, periodically checking cancellation_token. 12 | while (ms > 0 && !cancellation_token) { 13 | ms -= CHECK_PERIOD_MS; 14 | std::this_thread::sleep_for(100ms); 15 | } 16 | return cancellation_token; 17 | } 18 | 19 | int main() { 20 | std::atomic_bool cancellation_token{false}; 21 | std::cout << "Starting long running tasks..." << std::endl; 22 | 23 | std::packaged_task task1(long_running_task); 24 | std::future result1 = task1.get_future(); 25 | std::jthread t1(std::move(task1), 500, std::ref(cancellation_token)); 26 | 27 | std::packaged_task task2(long_running_task); 28 | std::future result2 = task2.get_future(); 29 | std::jthread t2(std::move(task2), 1000, std::ref(cancellation_token)); 30 | 31 | std::cout << "Cancelling tasks after 600 ms..." << std::endl; 32 | std::this_thread::sleep_for(600ms); 33 | cancellation_token = true; 34 | 35 | std::cout << "Task1, waiting for 500 ms. Cancelled = " << std::boolalpha << result1.get() << std::endl; 36 | std::cout << "Task2, waiting for 1 second. Cancelled = " << std::boolalpha << result2.get() << std::endl; 37 | } -------------------------------------------------------------------------------- /Chapter_06/6x10-combined_results.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std::literals; 7 | 8 | // combineFunc will run in a thread, spanning 2 extra threads, 9 | // one for computing a value, another to retrieve a value from a file. 10 | // Both results will be returned to the main thread as a combined promise. 11 | void combineFunc(std::promise> combineProm) { 12 | try { 13 | // Thread to simulate computing a value. 14 | std::cout << "Starting computeThread..." << std::endl; 15 | auto computeVal = [](std::promise prom) mutable { 16 | std::this_thread::sleep_for(1s); 17 | prom.set_value(42); 18 | }; 19 | std::promise computeProm; 20 | auto computeFut = computeProm.get_future(); 21 | std::jthread computeThread(computeVal, std::move(computeProm)); 22 | 23 | // Thread to simulate downloading a file. 24 | std::cout << "Starting dataThread..." << std::endl; 25 | auto fetchData = [](std::promise prom) mutable { 26 | std::this_thread::sleep_for(2s); 27 | prom.set_value("data.txt"s); 28 | }; 29 | std::promise fetchProm; 30 | auto fetchFut = fetchProm.get_future(); 31 | std::jthread dataThread(fetchData, std::move(fetchProm)); 32 | 33 | combineProm.set_value({computeFut.get(), fetchFut.get()}); 34 | } catch (...) { 35 | combineProm.set_exception(std::current_exception()); 36 | } 37 | } 38 | 39 | int main() { 40 | // Create combined promise. Get results, waiting for threads 41 | // if not finished yet. 42 | std::cout << "Creating combined promise..." << std::endl; 43 | std::promise> combineProm; 44 | auto combineFuture = combineProm.get_future(); 45 | std::jthread combineThread(combineFunc, std::move(combineProm)); 46 | 47 | // Access results in the main thread 48 | auto [data, file] = combineFuture.get(); 49 | std::cout << "Value [ " << data << " ] File [ " << file << " ]" << std::endl; 50 | 51 | return 0; 52 | } 53 | -------------------------------------------------------------------------------- /Chapter_06/6x11-pipeline.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std::chrono_literals; 7 | 8 | #define sync_cout std::osyncstream(std::cout) 9 | 10 | template 11 | class Task { 12 | public: 13 | // Constructor only accepting a function. 14 | // No dependencies with previous tasks. 15 | Task(int id, Func& func) : id_(id), func_(func), has_dependency_(false) { 16 | sync_cout << "Task " << id << " constructed without dependencies." << std::endl; 17 | fut_ = prom_.get_future().share(); 18 | } 19 | 20 | // Constructor accepting a variable number of std::future 21 | // defining dependencies with previous tasks. 22 | template 23 | Task(int id, Func& func, Futures&&... futures) : id_(id), func_(func), has_dependency_(true) { 24 | sync_cout << "Task " << id << " constructed with dependencies." << std::endl; 25 | fut_ = prom_.get_future().share(); 26 | add_dependencies(std::forward(futures)...); 27 | } 28 | 29 | // Return future for this task to create dependency chain. 30 | std::shared_future get_dependency() { 31 | sync_cout << "Getting future from task " << id_ << std::endl; 32 | return fut_; 33 | } 34 | 35 | // Run the task waiting for dependant tasks. 36 | void operator()() { 37 | sync_cout << "Running task " << id_ << std::endl; 38 | 39 | // Wait for each future to be completed 40 | wait_completion(); 41 | 42 | // Run task function. 43 | func_(); 44 | 45 | // Signal dependent tasks about completion. 46 | sync_cout << "Signaling completion of task " << id_ << std::endl; 47 | prom_.set_value(); 48 | } 49 | 50 | private: 51 | // Add futures to the futures vector using variadic templates. 52 | template 53 | void add_dependencies(Futures&&... futures) { 54 | (deps_.push_back(futures), ...); 55 | } 56 | 57 | // Wait for tasks that depends on to be completed. 58 | void wait_completion() { 59 | sync_cout << "Waiting completion for task " << id_ << std::endl; 60 | if (!deps_.empty()) { 61 | for (auto& fut : deps_) { 62 | if (fut.valid()) { 63 | sync_cout << "Fut valid so getting value in task " << id_ << std::endl; 64 | fut.get(); 65 | } 66 | } 67 | } 68 | } 69 | 70 | private: 71 | int id_; 72 | Func& func_; 73 | 74 | std::promise prom_; // Signal task completion 75 | std::shared_future fut_; // Own future to share with dependant tasks 76 | std::vector> deps_; // Wait for tasks that this task depends on for completion 77 | 78 | bool has_dependency_; 79 | }; 80 | 81 | int main() { 82 | // Tasks to run in the different pipeline steps 83 | auto sleep1s = []() { std::this_thread::sleep_for(1s); }; 84 | auto sleep2s = []() { std::this_thread::sleep_for(2s); }; 85 | 86 | // Tasks creation. 87 | Task task1(1, sleep1s); 88 | Task task2(2, sleep2s, task1.get_dependency()); 89 | Task task3(3, sleep1s, task2.get_dependency()); 90 | Task task4(4, sleep2s, task2.get_dependency()); 91 | Task task5(5, sleep2s, task3.get_dependency(), task4.get_dependency()); 92 | 93 | // Starting the pipeline 94 | // Launch all tasks. They will be waiting for their dependency promise to be triggered. 95 | // As Task #1 has no dependencies, it will start straight away. 96 | // Other tasks will be waiting until other previous tasks finish. 97 | sync_cout << "Starting the pipeline..." << std::endl; 98 | task1(); 99 | task2(); 100 | task3(); 101 | task4(); 102 | task5(); 103 | 104 | // Wait for pipeline to complete by checking the future of last task. 105 | sync_cout << "Waiting for the pipeline to finish..." << std::endl; 106 | auto finish_pipeline_fut = task5.get_dependency(); 107 | finish_pipeline_fut.get(); 108 | 109 | sync_cout << "All done!" << std::endl; 110 | return 0; 111 | } 112 | -------------------------------------------------------------------------------- /Chapter_06/6x12-producer_consumer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | const unsigned VALUE_RANGE = 1000; 9 | const unsigned RESULTS_TO_PRODUCE = 10; // Numbers of items to produce. 10 | const unsigned MAX_WAIT = 500; // Maximum waiting time (ms) when producing items. 11 | 12 | 13 | template 14 | class ThreadSafeQueue { 15 | public: 16 | void push(T value) { 17 | std::lock_guard lock(mutex_); 18 | queue_.push(std::move(value)); 19 | cond_var_.notify_one(); 20 | } 21 | 22 | T pop() { 23 | std::unique_lock lock(mutex_); 24 | cond_var_.wait(lock, [&] { return !queue_.empty(); }); 25 | T value = std::move(queue_.front()); 26 | queue_.pop(); 27 | return value; 28 | } 29 | 30 | private: 31 | std::queue queue_; 32 | std::mutex mutex_; 33 | std::condition_variable cond_var_; 34 | }; 35 | 36 | using TaskQueue = ThreadSafeQueue>; 37 | 38 | void producer(TaskQueue& queue, int val) { 39 | // Create a new promise 40 | std::promise prom; 41 | auto fut = prom.get_future(); 42 | 43 | // Move future into the queue 44 | queue.push(std::move(fut)); 45 | 46 | // Simulate some work in the producer side before setting the value in the promise 47 | std::this_thread::sleep_for(std::chrono::milliseconds(rand() % MAX_WAIT)); 48 | 49 | // Set the value in the promise 50 | prom.set_value(val); 51 | } 52 | 53 | void consumer(TaskQueue& queue) { 54 | // Simulate some work in the consumer side before receiving the future 55 | std::this_thread::sleep_for(std::chrono::milliseconds(rand() % MAX_WAIT)); 56 | 57 | // Extract a future from the queue 58 | std::future fut = queue.pop(); 59 | 60 | // Retrieve the result from the future 61 | try { 62 | int result = fut.get(); 63 | std::cout << "Result: " << result << std::endl; 64 | } catch (const std::exception& e) { 65 | std::cerr << "Exception: " << e.what() << std::endl; 66 | } 67 | } 68 | 69 | int main() { 70 | TaskQueue queue; 71 | 72 | auto producerFunc = [](TaskQueue& queue) { 73 | auto n = RESULTS_TO_PRODUCE; 74 | while (n-- > 0) { 75 | int val = rand() % VALUE_RANGE; 76 | std::cout << "Producer: Sending value " << val << std::endl; 77 | producer(queue, val); 78 | } 79 | }; 80 | 81 | auto consumerFunc = [](TaskQueue& queue) { 82 | auto n = RESULTS_TO_PRODUCE; 83 | while (n-- > 0) { 84 | std::cout << "Consumer: Receiving value" << std::endl; 85 | consumer(queue); 86 | } 87 | }; 88 | 89 | std::jthread producerThread(producerFunc, std::ref(queue)); 90 | std::jthread consumerThread(consumerFunc, std::ref(queue)); 91 | 92 | return 0; 93 | } 94 | -------------------------------------------------------------------------------- /Chapter_06/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Output directory 2 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BINARY_DIR}/Chapter_06) 3 | 4 | # Create executable for each cpp file 5 | file(GLOB SOURCES "*.cpp") 6 | foreach(SOURCE ${SOURCES}) 7 | get_filename_component(EXECUTABLE_NAME ${SOURCE} NAME_WE) 8 | add_executable(${EXECUTABLE_NAME} ${SOURCE}) 9 | target_link_libraries(${EXECUTABLE_NAME} ${Boost_LIBRARIES}) 10 | endforeach() 11 | -------------------------------------------------------------------------------- /Chapter_07/7x01-async_function_invocation.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void func() { 5 | std::cout << "Using function pointer" << std::endl; 6 | } 7 | 8 | class FuncObjectClass { 9 | public: 10 | void operator()() { 11 | std::cout << "Using function object class" << std::endl; 12 | } 13 | }; 14 | 15 | int main() { 16 | // Run function asynchronously from a function pointer. 17 | auto fut1 = std::async(func); 18 | 19 | // Run function asynchronously using a lambda function 20 | auto lambda_func = []() { 21 | std::cout << "Using lambda function\n"; 22 | }; 23 | auto fut2 = std::async(lambda_func); 24 | 25 | // Run function asynchronously using an embedded lambda function 26 | auto fut3 = std::async([]() { 27 | std::cout << "Using embedded lambda function\n"; 28 | }); 29 | 30 | // Run function asynchronously using a function object (overloading operator() ) 31 | // FuncObjectClass obj1; 32 | auto fut4 = std::async(FuncObjectClass()); 33 | 34 | // Run function asynchronously using a non-static member function 35 | class Obj { 36 | public: 37 | void func() { 38 | std::cout << "Using a non-static member function\n"; 39 | } 40 | static void static_func() { 41 | std::cout << "Using a static member function\n"; 42 | } 43 | }; 44 | 45 | Obj obj2; 46 | 47 | // In this case, the first argument is the reference to the function 48 | // and second is the object reference. 49 | auto fut5 = std::async(&Obj::func, &obj2); 50 | 51 | // Run function asynchronously using a static member function. Only the first argument is needed. 52 | auto fut6 = std::async(&Obj::static_func); 53 | 54 | // Wait for async functions to finish. 55 | fut1.get(); 56 | fut2.get(); 57 | fut3.get(); 58 | fut4.get(); 59 | fut5.get(); 60 | fut6.get(); 61 | return 0; 62 | } -------------------------------------------------------------------------------- /Chapter_07/7x02-async_passing_values.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define sync_cout std::osyncstream(std::cout) 8 | 9 | // Passing arguments by value 10 | void funcByValue(const std::string& str, int val) { 11 | sync_cout << "str: " << str << ", val: " << val << std::endl; 12 | } 13 | 14 | // Passing arguments by reference 15 | void modifyValues(std::string& str, int& val) { 16 | str += " (Thread)"; 17 | val++; 18 | } 19 | 20 | void printVector(const std::vector& v) { 21 | sync_cout << "Vector: "; 22 | for (int num : v) { 23 | sync_cout << num << " "; 24 | } 25 | sync_cout << std::endl; 26 | } 27 | 28 | int main() { 29 | // Passing arguments by value 30 | std::string str1{"Passing by value"}; 31 | auto fut1 = async(funcByValue, str1, 1); 32 | 33 | // Passing arguments by reference 34 | std::string str2{"Passing by reference"}; 35 | int val = 1; 36 | auto fut2 = std::async(modifyValues, std::ref(str2), std::ref(val)); 37 | fut2.get(); 38 | sync_cout << "str: " << str2 << ", val: " << val << std::endl; 39 | 40 | // Passing argument by const reference 41 | std::vector v{1, 2, 3, 4, 5}; 42 | auto fut3 = std::async(printVector, std::cref(v)); 43 | fut3.get(); 44 | 45 | // Moving element into a thread 46 | auto fut4 = std::async(printVector, std::move(v)); 47 | fut4.get(); 48 | 49 | // Note: Trying to access v here would result in undefined behavior 50 | // as V was moved into the thread and not usable anymore. 51 | 52 | // Lambda function with captures 53 | std::string str5{"Hello"}; 54 | auto fut5 = std::async([&]() { 55 | sync_cout << "str: " << str5 << std::endl; 56 | }); 57 | fut5.get(); 58 | 59 | return 0; 60 | } 61 | -------------------------------------------------------------------------------- /Chapter_07/7x03-async_returning_values.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define sync_cout std::osyncstream(std::cout) 10 | 11 | using namespace std::chrono_literals; 12 | 13 | int compute(unsigned taskId, int x, int y) { 14 | std::this_thread::sleep_for(std::chrono::milliseconds(rand() % 200)); // Simulate long computation 15 | sync_cout << "Running task " << taskId << '\n'; 16 | return std::pow(x, y); 17 | } 18 | 19 | int main() { 20 | // Launch compute asynchronously 21 | std::vector> futVec; 22 | for (int i = 0; i <= 10; i++) 23 | futVec.emplace_back(std::async(compute, i+1, 2, i)); 24 | 25 | // Do other work in the main thread 26 | sync_cout << "Waiting in main thread\n"; 27 | std::this_thread::sleep_for(1s); 28 | 29 | // Waiting for results and storing in output vector 30 | std::vector results; 31 | for (auto& fut : futVec) 32 | results.push_back(fut.get()); 33 | 34 | // Showing results 35 | for (auto& res : results) 36 | std::cout << res << ' '; 37 | std::cout << std::endl; 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /Chapter_07/7x04-async_launch_policies.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define sync_cout std::osyncstream(std::cout) 8 | 9 | using namespace std::chrono_literals; 10 | 11 | int square(const std::string& task_name, int x) { 12 | sync_cout << "Launching " << task_name << " task...\n"; 13 | return x * x; 14 | } 15 | 16 | int main() { 17 | sync_cout << "Starting main thread...\n"; 18 | 19 | auto fut_async = std::async(std::launch::async, 20 | square, "async_policy", 2); 21 | auto fut_deferred = std::async(std::launch::deferred, 22 | square, "deferred_policy", 3); 23 | auto fut_default = std::async(square, 24 | "default_policy", 4); 25 | 26 | auto is_deferred = [](std::future& fut) { 27 | return (fut.wait_for(0s) == std::future_status::deferred); 28 | }; 29 | 30 | sync_cout << "Checking if deferred:\n"; 31 | sync_cout << " fut_async: " << std::boolalpha 32 | << is_deferred(fut_async) << '\n'; 33 | sync_cout << " fut_deferred: " << std::boolalpha 34 | << is_deferred(fut_deferred) << '\n'; 35 | sync_cout << " fut_default: " << std::boolalpha 36 | << is_deferred(fut_default) << '\n'; 37 | 38 | sync_cout << "Waiting in main thread...\n"; 39 | std::this_thread::sleep_for(1s); 40 | sync_cout << "Wait in main thread finished.\n"; 41 | 42 | sync_cout << "Getting result from " 43 | << "async policy task...\n"; 44 | int val_async = fut_async.get(); 45 | sync_cout << "Result from async policy task: " 46 | << val_async << '\n'; 47 | 48 | sync_cout << "Getting result from " 49 | << "deferred policy task...\n"; 50 | int val_deferred = fut_deferred.get(); 51 | sync_cout << "Result from deferred policy task: " 52 | << val_deferred << '\n'; 53 | 54 | sync_cout << "Getting result from " 55 | << "default policy task...\n"; 56 | int val_default = fut_default.get(); 57 | sync_cout << "Result from default policy task: " 58 | << val_default << '\n'; 59 | 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /Chapter_07/7x05-async_handling_exceptions.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | void print_exceptions(const std::exception& e, int level = 1) { 8 | auto indent = std::string(2 * level, ' '); 9 | std::cerr << indent << e.what() << '\n'; 10 | try { 11 | std::rethrow_if_nested(e); 12 | } catch (const std::exception& nestedException) { 13 | print_exceptions(nestedException, level + 1); 14 | } catch (...) { } 15 | } 16 | 17 | void func_throwing() { 18 | // Asynchronous function throwing an exception. 19 | throw std::runtime_error("Exception in func_throwing."); 20 | } 21 | 22 | int main() { 23 | auto fut = std::async([]() { 24 | try { 25 | func_throwing(); 26 | } catch (...) { 27 | // Rethrow any catched exception. 28 | std::throw_with_nested(std::runtime_error("Exception in async task.")); 29 | } 30 | }); 31 | 32 | // Main thread: Catch exceptions and print them. 33 | try { 34 | fut.get(); 35 | } catch (const std::exception& e) { 36 | std::cerr << "Caught exceptions:\n"; 37 | print_exceptions(e); 38 | } 39 | 40 | return 0; 41 | } 42 | -------------------------------------------------------------------------------- /Chapter_07/7x06-async_future_behavior.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define sync_cout std::osyncstream(std::cout) 7 | 8 | using namespace std::chrono_literals; 9 | 10 | unsigned func(unsigned x) { 11 | std::this_thread::sleep_for(10ms); 12 | return 2 * x; 13 | } 14 | 15 | int main() { 16 | constexpr unsigned NUM_TASKS = 32; 17 | 18 | // Prints duration from start time. 19 | auto duration_from = [](auto start) { 20 | auto dur = std::chrono::high_resolution_clock::now() - start; 21 | return std::chrono::duration_cast(dur).count(); 22 | }; 23 | 24 | // Show results vector size and elements. 25 | auto show_results = [](const std::vector& res) { 26 | std::cout << "Results [" << res.size() << "] "; 27 | for (auto r : res) { 28 | std::cout << r << " "; 29 | } 30 | std::cout << '\n'; 31 | }; 32 | 33 | // Running several tasks without storing the future. 34 | auto start = std::chrono::high_resolution_clock::now(); 35 | for (unsigned i = 0; i < NUM_TASKS; i++) { 36 | std::async(std::launch::async, func, i); 37 | } 38 | std::cout << "Discarding futures: " << duration_from(start) << '\n'; 39 | 40 | // Reusing a future, discarding previous tasks. 41 | start = std::chrono::high_resolution_clock::now(); 42 | for (unsigned i = 0; i < NUM_TASKS; i++) { 43 | auto fut = std::async(std::launch::async, func, i); 44 | } 45 | std::cout << "In-place futures: " << duration_from(start) << '\n'; 46 | 47 | // Reusing a future, discarding previous tasks. 48 | std::future fut; 49 | start = std::chrono::high_resolution_clock::now(); 50 | for (unsigned i = 0; i < NUM_TASKS; i++) { 51 | fut = std::async(std::launch::async, func, i); 52 | } 53 | std::cout << "Reusing future: " << duration_from(start) << '\n'; 54 | 55 | // Same as before but waiting for tasks to finish. 56 | std::vector res; 57 | start = std::chrono::high_resolution_clock::now(); 58 | for (unsigned i = 0; i < NUM_TASKS; i++) { 59 | auto fut = std::async(std::launch::async, func, i); 60 | res.push_back(fut.get()); 61 | } 62 | std::cout << "Reused future and storing results: " << duration_from(start) << '\n'; 63 | show_results(res); 64 | 65 | // Running several tasks storing a future per task in a vector and waiting for futures afterwards. 66 | std::vector> futsVec; 67 | res.clear(); 68 | start = std::chrono::high_resolution_clock::now(); 69 | for (unsigned i = 0; i < NUM_TASKS; i++) { 70 | futsVec.emplace_back(std::async(std::launch::async, func, i)); 71 | } 72 | for (unsigned i = 0; i < NUM_TASKS; i++) { 73 | res.push_back( futsVec[i].get() ); 74 | } 75 | std::cout << "Futures vector and storing results: " << duration_from(start) << '\n'; 76 | show_results(res); 77 | 78 | return 0; 79 | } 80 | -------------------------------------------------------------------------------- /Chapter_07/7x07-thread-limiber.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define sync_cout std::osyncstream(std::cout) 9 | 10 | using namespace std::chrono_literals; 11 | 12 | void task(int id, std::counting_semaphore<>& sem) { 13 | // Acquire the semaphore 14 | sem.acquire(); 15 | 16 | // Simulate some work 17 | sync_cout << "Running task " << id << "...\n"; 18 | std::this_thread::sleep_for(1s); 19 | 20 | // Release the semaphore 21 | sem.release(); 22 | } 23 | 24 | int main() { 25 | const int total_tasks = 20; 26 | const int max_concurrent_tasks = std::thread::hardware_concurrency(); 27 | std::counting_semaphore<> sem(max_concurrent_tasks); 28 | 29 | sync_cout << "Allowing only " << max_concurrent_tasks 30 | << " concurrent tasks to run " << total_tasks << " tasks.\n"; 31 | 32 | std::vector> futures; 33 | for (int i = 0; i < total_tasks; ++i) { 34 | futures.push_back(std::async(std::launch::async, task, i, std::ref(sem))); 35 | } 36 | 37 | for (auto& fut : futures) { 38 | fut.get(); 39 | } 40 | std::cout << "All tasks completed." << std::endl; 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /Chapter_07/7x08-data_aggregation.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int square(int x) { return x * x; } 7 | 8 | int sum_results(std::vector>& futures) { 9 | int sum = 0; 10 | for (auto& fut : futures) { 11 | sum += fut.get(); 12 | } 13 | return sum; 14 | } 15 | 16 | double average_squares(int n) { 17 | std::vector> futures; 18 | 19 | for (int i = 1; i <= n; ++i) { 20 | futures.push_back(std::async(std::launch::async, square, i)); 21 | } 22 | 23 | return double(sum_results(futures)) / n; 24 | } 25 | 26 | int main() { 27 | int N = 100; 28 | std::cout << std::fixed << std::setprecision(2); 29 | std::cout << "Average of squares for N = " << N << " is " << average_squares(N) << '\n'; 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /Chapter_07/7x09-async_search_across_containers.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | template 11 | bool search(const C& container, const std::string& target) { 12 | return std::find(container.begin(), container.end(), target) != container.end(); 13 | } 14 | 15 | int main() { 16 | std::vector africanAnimals = {"elephant", "giraffe", "lion", "zebra"}; 17 | std::list americanAnimals = {"alligator", "bear", "eagle", "puma"}; 18 | std::forward_list asianAnimals = {"orangutan", "panda", "tapir", "tiger"}; 19 | std::set europeanAnimals = {"deer", "hedgehog", "linx", "wolf"}; 20 | 21 | std::string target = "elephant"; 22 | 23 | // Launch asynchronous searches on different containers 24 | auto fut1 = std::async(std::launch::async, search>, africanAnimals, target); 25 | auto fut2 = std::async(std::launch::async, search>, americanAnimals, target); 26 | auto fut3 = std::async(std::launch::async, search>, asianAnimals, target); 27 | auto fut4 = std::async(std::launch::async, search>, europeanAnimals, target); 28 | 29 | // Collect results 30 | bool found = fut1.get() || fut2.get() || fut3.get() || fut4.get(); 31 | if (found) { 32 | std::cout << target << " found in one of the containers.\n"; 33 | } else { 34 | std::cout << target << " not found in any of the containers.\n"; 35 | } 36 | 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /Chapter_07/7x10-async_search_large_vector.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // Generate a large vector of random integers using a uniform distribution 9 | std::vector generate_vector(size_t size) { 10 | std::vector vec(size); 11 | std::random_device rd; 12 | std::mt19937 gen(rd()); 13 | std::uniform_int_distribution<> dist(1, size); 14 | std::generate(vec.begin(), vec.end(), [&]() { 15 | return dist(gen); 16 | }); 17 | return vec; 18 | } 19 | 20 | // Search for a value in a segment of a vector 21 | bool search_segment(const std::vector& vec, int target, size_t begin, size_t end) { 22 | auto begin_it = vec.begin() + begin; 23 | auto end_it = vec.begin() + end; 24 | return std::find(begin_it, end_it, target) != end_it; 25 | } 26 | 27 | int main() { 28 | const int target = 100; 29 | 30 | // Large vector 31 | std::vector vec = generate_vector(5000000); 32 | auto vec_size = vec.size(); 33 | 34 | // Number of segments to divide vector and search 35 | const size_t num_segments = 16; 36 | size_t segment_size = vec.size() / num_segments; 37 | 38 | // Launch asynchronous search tasks 39 | std::vector> futs; 40 | for (size_t i = 0; i < num_segments; ++i) { 41 | auto begin = std::min(i * segment_size, vec_size); 42 | auto end = std::min((i + 1) * segment_size, vec_size); 43 | futs.push_back( std::async(std::launch::async, search_segment, std::cref(vec), target, begin, end) ); 44 | } 45 | 46 | // Collect results 47 | bool found = false; 48 | for (auto& fut : futs) { 49 | if (fut.get()) { 50 | found = true; 51 | break; 52 | } 53 | } 54 | 55 | if (found) { 56 | std::cout << "Target " << target << " found in the large vector.\n"; 57 | } else { 58 | std::cout << "Target " << target << " not found in the large vector.\n"; 59 | } 60 | 61 | return 0; 62 | } 63 | -------------------------------------------------------------------------------- /Chapter_07/7x11-async_matrix_multiplication.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using matrix_t = std::vector>; 8 | 9 | int dot_product(const std::vector& a, const std::vector& b) { 10 | int sum = 0; 11 | for (std::size_t i = 0; i < a.size(); ++i) { 12 | sum += a[i] * b[i]; 13 | } 14 | return sum; 15 | } 16 | 17 | // Function to perform matrix multiplication 18 | matrix_t matrix_multiply(const matrix_t& A, const matrix_t& B) { 19 | if (A[0].size() != B.size()) { 20 | throw new std::runtime_error("Wrong matrices dimmensions."); 21 | } 22 | std::size_t rows = A.size(); 23 | std::size_t cols = B[0].size(); 24 | std::size_t inner_dim = B.size(); 25 | matrix_t res(rows, std::vector(cols, 0)); 26 | 27 | // Launch one task when multiplying row from A and column from B 28 | std::vector> futs; 29 | for (std::size_t i = 0; i < rows; ++i) { 30 | for (std::size_t j = 0; j < cols; ++j) { 31 | // Extract column j from B 32 | std::vector column(inner_dim); 33 | for (std::size_t k = 0; k < inner_dim; ++k) { 34 | column[k] = B[k][j]; 35 | } 36 | // Multiply row i from A and column j from B 37 | futs.push_back(std::async(std::launch::async, dot_product, A[i], column)); 38 | } 39 | } 40 | 41 | // Collect results 42 | for (std::size_t i = 0; i < rows; ++i) { 43 | for (std::size_t j = 0; j < cols; ++j) { 44 | res[i][j] = futs[i * cols + j].get(); 45 | } 46 | } 47 | return res; 48 | } 49 | 50 | int main() { 51 | auto show_matrix = [](const std::string& name, matrix_t& mtx) { 52 | std::cout << name << '\n'; 53 | for (const auto& row : mtx) { 54 | for (const auto& elem : row) { 55 | std::cout << elem << " "; 56 | } 57 | std::cout << '\n'; 58 | } 59 | std::cout << std::endl; 60 | }; 61 | 62 | matrix_t A = {{1, 2, 3}, 63 | {4, 5, 6}}; 64 | 65 | matrix_t B = {{7, 8, 9}, 66 | {10, 11, 12}, 67 | {13, 14, 15}}; 68 | 69 | auto res = matrix_multiply(A, B); 70 | 71 | show_matrix("A", A); 72 | show_matrix("B", B); 73 | show_matrix("Result", res); 74 | 75 | return 0; 76 | } 77 | -------------------------------------------------------------------------------- /Chapter_07/7x12-chaining_tasks.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int stage1(int x) { 6 | if (x < 0) throw std::runtime_error("Negative input not allowed"); 7 | return x + 10; 8 | } 9 | 10 | int stage2(int x) { 11 | return x * 2; 12 | } 13 | 14 | int stage3(int x) { 15 | return x - 5; 16 | } 17 | 18 | int main() { 19 | int input_value = 5; 20 | 21 | try { 22 | auto fut1 = std::async(std::launch::async, stage1, input_value); 23 | 24 | auto fut2 = std::async(std::launch::async, [&fut1]() { return stage2(fut1.get()); }); 25 | 26 | auto fut3 = std::async(std::launch::async, [&fut2]() { return stage3(fut2.get()); }); 27 | 28 | int final_result = fut3.get(); 29 | std::cout << "Final Result: " << final_result << std::endl; 30 | 31 | } catch (const std::exception &ex) { 32 | std::cerr << "Exception caught: " << ex.what() << std::endl; 33 | } 34 | 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /Chapter_07/7x13-pipeline.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std::chrono_literals; 7 | 8 | #define sync_cout std::osyncstream(std::cout) 9 | 10 | template 11 | class Task { 12 | public: 13 | // Constructor only accepting a function. 14 | // No dependencies with previous tasks. 15 | Task(int id, Func& func) : id_(id), func_(func), has_dependency_(false) { 16 | sync_cout << "Task " << id << " constructed without dependencies.\n"; 17 | fut_ = std::async(std::launch::deferred, [this](){ (*this)(); }); 18 | } 19 | 20 | // Constructor accepting a variable number of std::future 21 | // defining dependencies with previous tasks. 22 | template 23 | Task(int id, Func& func, Futures&&... futures) : id_(id), func_(func), has_dependency_(true) { 24 | sync_cout << "Task " << id << " constructed with dependencies.\n"; 25 | fut_ = std::async(std::launch::deferred, [this](){ (*this)(); }); 26 | add_dependencies(std::forward(futures)...); 27 | } 28 | 29 | // Return future for this task to create dependency chain. 30 | std::shared_future get_dependency() { 31 | sync_cout << "Getting future from task " << id_ << std::endl; 32 | return fut_.share(); 33 | } 34 | 35 | // Run the task waiting for dependant tasks. 36 | void operator()() { 37 | sync_cout << "Starting task " << id_ << std::endl; 38 | 39 | // Wait for each future to be completed 40 | wait_completion(); 41 | 42 | // Run task function. 43 | sync_cout << "Running task " << id_ << std::endl; 44 | func_(); 45 | } 46 | 47 | // Start asynchronous task 48 | void start() { 49 | fut_.get(); 50 | } 51 | 52 | private: 53 | // Add futures to the futures vector using variadic templates. 54 | template 55 | void add_dependencies(Futures&&... futures) { 56 | (deps_.push_back(futures), ...); 57 | } 58 | 59 | // Wait for tasks that depends on to be completed. 60 | void wait_completion() { 61 | sync_cout << "Waiting completion for task " << id_ << std::endl; 62 | if (!deps_.empty()) { 63 | for (auto& fut : deps_) { 64 | if (fut.valid()) { 65 | sync_cout << "Fut valid so getting value in task " << id_ << std::endl; 66 | fut.get(); 67 | } 68 | } 69 | } 70 | } 71 | 72 | private: 73 | int id_; 74 | Func& func_; 75 | std::future fut_; 76 | std::vector> deps_; 77 | bool has_dependency_; 78 | }; 79 | 80 | int main() { 81 | // Tasks to run in the different pipeline steps 82 | auto sleep1s = []() { std::this_thread::sleep_for(1s); }; 83 | auto sleep2s = []() { std::this_thread::sleep_for(2s); }; 84 | 85 | // Tasks creation. 86 | Task task1(1, sleep1s); 87 | Task task2(2, sleep2s, task1.get_dependency()); 88 | Task task3(3, sleep1s, task2.get_dependency()); 89 | Task task4(4, sleep2s, task2.get_dependency()); 90 | Task task5(5, sleep2s, task3.get_dependency(), task4.get_dependency()); 91 | 92 | // Starting the pipeline 93 | // Launch last tasks. It will recursivelly call their dependency tasks by 94 | // using the dependency future and trigger the deferred asynchronous tasks. 95 | sync_cout << "Starting the pipeline..." << std::endl; 96 | task5.start(); 97 | 98 | sync_cout << "All done!" << std::endl; 99 | return 0; 100 | } 101 | -------------------------------------------------------------------------------- /Chapter_07/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Output directory 2 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BINARY_DIR}/Chapter_07) 3 | 4 | # Create executable for each cpp file 5 | file(GLOB SOURCES "*.cpp") 6 | foreach(SOURCE ${SOURCES}) 7 | get_filename_component(EXECUTABLE_NAME ${SOURCE} NAME_WE) 8 | add_executable(${EXECUTABLE_NAME} ${SOURCE}) 9 | target_link_libraries(${EXECUTABLE_NAME} ${Boost_LIBRARIES}) 10 | endforeach() 11 | -------------------------------------------------------------------------------- /Chapter_08/8x01-simple_coroutine.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct return_type { 5 | struct promise_type { 6 | return_type get_return_object() noexcept { 7 | std::cout << "get_return_object" << std::endl; 8 | return return_type{ *this }; 9 | } 10 | 11 | void return_void() noexcept { 12 | std::cout << "return_void" << std::endl; 13 | } 14 | 15 | std::suspend_always initial_suspend() noexcept { 16 | std::cout << "initial_suspend" << std::endl; 17 | return {}; 18 | } 19 | 20 | std::suspend_always final_suspend() noexcept { 21 | std::cout << "final_suspend" << std::endl; 22 | return {}; 23 | } 24 | 25 | void unhandled_exception() noexcept { 26 | std::cout << "unhandled_exception" << std::endl; 27 | } 28 | }; 29 | 30 | explicit return_type(promise_type& promise) { 31 | std::cout << "return_type()" << std::endl; 32 | } 33 | 34 | ~return_type() noexcept { 35 | std::cout << "~return_type()" << std::endl; 36 | } 37 | }; 38 | 39 | return_type coro_func() { 40 | co_return; 41 | } 42 | 43 | int main() { 44 | auto rt = coro_func(); 45 | return 0; 46 | } -------------------------------------------------------------------------------- /Chapter_08/8x02-yield_coroutine.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std::string_literals; 6 | 7 | struct return_type { 8 | struct promise_type { 9 | std::string output_data { }; 10 | 11 | return_type get_return_object() noexcept { 12 | std::cout << "get_return_object" << std::endl; 13 | return return_type{ *this }; 14 | } 15 | 16 | void return_void() noexcept { 17 | std::cout << "return_void" << std::endl; 18 | } 19 | 20 | std::suspend_always yield_value(std::string msg) noexcept { 21 | std::cout << "yield_value" << std::endl; 22 | output_data = std::move(msg); 23 | return {}; 24 | } 25 | 26 | std::suspend_always initial_suspend() noexcept { 27 | std::cout << "initial_suspend" << std::endl; 28 | return {}; 29 | } 30 | 31 | std::suspend_always final_suspend() noexcept { 32 | std::cout << "final_suspend" << std::endl; 33 | return {}; 34 | } 35 | 36 | void unhandled_exception() noexcept { 37 | std::cout << "unhandled_exception" << std::endl; 38 | } 39 | }; 40 | 41 | std::coroutine_handle handle{}; 42 | 43 | explicit return_type(promise_type& promise) : handle{ std::coroutine_handle::from_promise(promise)} { 44 | std::cout << "return_type()" << std::endl; 45 | } 46 | 47 | ~return_type() noexcept { 48 | if (handle) { 49 | handle.destroy(); 50 | } 51 | 52 | std::cout << "~return_type()" << std::endl; 53 | } 54 | 55 | std::string get() { 56 | std::cout << "get()" << std::endl; 57 | 58 | if (!handle.done()) { 59 | handle.resume(); 60 | } 61 | return std::move(handle.promise().output_data); 62 | } 63 | }; 64 | 65 | return_type coro_func() { 66 | co_yield "Hello from the coroutine\n"s; 67 | 68 | co_return; 69 | } 70 | 71 | int main() { 72 | auto rt = coro_func(); 73 | std::cout << rt.get() << std::endl; 74 | return 0; 75 | } -------------------------------------------------------------------------------- /Chapter_08/8x03-waiting_coroutine.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std::string_literals; 6 | 7 | struct return_type { 8 | struct promise_type { 9 | std::string input_data { }; 10 | 11 | return_type get_return_object() noexcept { 12 | std::cout << "get_return_object" << std::endl; 13 | return return_type{ *this }; 14 | } 15 | 16 | void return_void() noexcept { 17 | std::cout << "return_void" << std::endl; 18 | } 19 | 20 | std::suspend_always initial_suspend() noexcept { 21 | std::cout << "initial_suspend" << std::endl; 22 | return {}; 23 | } 24 | 25 | std::suspend_always final_suspend() noexcept { 26 | std::cout << "final_suspend" << std::endl; 27 | return {}; 28 | } 29 | 30 | void unhandled_exception() noexcept { 31 | std::cout << "unhandled_exception" << std::endl; 32 | } 33 | 34 | auto await_transform(std::string) noexcept { 35 | struct awaiter { 36 | promise_type& promise; 37 | 38 | bool await_ready() const noexcept { 39 | std::cout << "await_ready()" << std::endl; 40 | return true; 41 | } 42 | 43 | std::string await_resume() const noexcept { 44 | std::cout << "await_resume()" << std::endl; 45 | return std::move(promise.input_data); 46 | } 47 | 48 | void await_suspend(std::coroutine_handle) const noexcept { 49 | std::cout << "await_suspend" << std::endl; 50 | } 51 | }; 52 | 53 | return awaiter(*this); 54 | 55 | } 56 | }; 57 | 58 | std::coroutine_handle handle{}; 59 | 60 | explicit return_type(promise_type& promise) : handle{ std::coroutine_handle::from_promise(promise)} { 61 | std::cout << "return_type()" << std::endl; 62 | } 63 | 64 | ~return_type() noexcept { 65 | if (handle) { 66 | handle.destroy(); 67 | } 68 | 69 | std::cout << "~return_type()" << std::endl; 70 | } 71 | 72 | void put(std::string msg) { 73 | handle.promise().input_data = std::move(msg); 74 | if (!handle.done()) { 75 | handle.resume(); 76 | } 77 | } 78 | }; 79 | 80 | return_type coro_func() { 81 | std::cout << co_await std::string{ }; 82 | co_return; 83 | } 84 | 85 | int main() { 86 | auto rt = coro_func(); 87 | rt.put("Hello from main\n"s); 88 | return 0; 89 | } -------------------------------------------------------------------------------- /Chapter_08/8x04-fibonacci_generator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | 6 | using namespace std::string_literals; 7 | 8 | template 9 | struct sequence_generator { 10 | struct promise_type { 11 | Out output_data { }; 12 | 13 | sequence_generator get_return_object() noexcept { 14 | return sequence_generator{ *this }; 15 | } 16 | 17 | void return_void() noexcept { 18 | } 19 | 20 | std::suspend_always initial_suspend() noexcept { 21 | return {}; 22 | } 23 | 24 | std::suspend_always final_suspend() noexcept { 25 | return {}; 26 | } 27 | 28 | void unhandled_exception() noexcept { 29 | } 30 | 31 | std::suspend_always yield_value(int64_t num) noexcept { 32 | output_data = num; 33 | return {}; 34 | } 35 | }; 36 | 37 | std::coroutine_handle handle{}; 38 | 39 | explicit sequence_generator(promise_type& promise) : handle{ std::coroutine_handle::from_promise(promise)} { 40 | } 41 | 42 | ~sequence_generator() noexcept { 43 | if (handle) { 44 | handle.destroy(); 45 | } 46 | } 47 | 48 | void next() { 49 | if (!handle.done()) { 50 | handle.resume(); 51 | } 52 | } 53 | 54 | int64_t value() { 55 | return handle.promise().output_data; 56 | } 57 | }; 58 | 59 | sequence_generator fibonacci() { 60 | int64_t a{ 0 }; 61 | int64_t b{ 1 }; 62 | int64_t c{ 0 }; 63 | 64 | while (true) { 65 | co_yield a; 66 | c = a + b; 67 | a = b; 68 | b = c; 69 | } 70 | } 71 | 72 | int main() { 73 | sequence_generator fib = fibonacci(); 74 | 75 | std::cout << "Generate ten Fibonacci numbers\n"s; 76 | 77 | for (int i = 0; i < 10; ++i) { 78 | fib.next(); 79 | std::cout << fib.value() << " "; 80 | } 81 | std::cout << std::endl; 82 | 83 | std::cout << "Generate ten more\n"s; 84 | 85 | for (int i = 0; i < 10; ++i) { 86 | fib.next(); 87 | std::cout << fib.value() << " "; 88 | } 89 | std::cout << std::endl; 90 | 91 | std::cout << "Let's do five more\n"s; 92 | 93 | for (int i = 0; i < 5; ++i) { 94 | fib.next(); 95 | std::cout << fib.value() << " "; 96 | } 97 | std::cout << std::endl; 98 | 99 | return 0; 100 | } -------------------------------------------------------------------------------- /Chapter_08/8x05-generator_cpp23.cpp: -------------------------------------------------------------------------------- 1 | // Available in C++23 with GCC14. 2 | // Clang does not support std::generator. 3 | #include 4 | #include 5 | 6 | std::generator fibonacci_generator() { 7 | int a{ }; 8 | int b{ 1 }; 9 | 10 | while (true) { 11 | co_yield a; 12 | int c = a + b; 13 | a = b; 14 | b = c; 15 | } 16 | } 17 | 18 | std::generator fibonacci_generator(int limit) { 19 | int a{ }; 20 | int b{ 1 }; 21 | 22 | while (limit--) { 23 | co_yield a; 24 | int c = a + b; 25 | a = b; 26 | b = c; 27 | } 28 | } 29 | 30 | int main() { 31 | 32 | auto fib = fibonacci_generator(); 33 | 34 | 35 | int i = 0; 36 | for (auto f = fib.begin(); f != fib.end(); ++f) { 37 | if (i == 10) { 38 | break; 39 | } 40 | std::cout << *f << " "; 41 | ++i; 42 | } 43 | std::cout << std::endl; 44 | 45 | for (int f : fibonacci_generator(10)) { 46 | std::cout << f << " "; 47 | } 48 | 49 | return 0; 50 | } -------------------------------------------------------------------------------- /Chapter_08/8x06-integer_parser.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std::string_literals; 11 | using namespace std::chrono_literals; 12 | 13 | template 14 | struct async_parse { 15 | struct promise_type { 16 | std::optional input_data { }; 17 | Out output_data { }; 18 | 19 | async_parse get_return_object() noexcept { 20 | return async_parse{ *this }; 21 | } 22 | 23 | void return_void() noexcept { 24 | } 25 | 26 | std::suspend_always initial_suspend() noexcept { 27 | return {}; 28 | } 29 | 30 | std::suspend_always final_suspend() noexcept { 31 | return {}; 32 | } 33 | 34 | void unhandled_exception() noexcept { 35 | } 36 | 37 | std::suspend_always yield_value(int64_t num) noexcept { 38 | output_data = num; 39 | return {}; 40 | } 41 | 42 | auto await_transform(char) noexcept { 43 | struct awaiter { 44 | promise_type& promise; 45 | 46 | [[nodiscard]] bool await_ready() const noexcept { 47 | return promise.input_data.has_value(); 48 | } 49 | 50 | [[nodiscard]] char await_resume() const noexcept { 51 | assert (promise.input_data.has_value()); 52 | return *std::exchange(promise.input_data, std::nullopt); 53 | } 54 | 55 | void await_suspend(std::coroutine_handle) const noexcept { 56 | } 57 | }; 58 | 59 | return awaiter(*this); 60 | 61 | } 62 | }; 63 | 64 | std::coroutine_handle handle{}; 65 | 66 | explicit async_parse(promise_type& promise) : handle{ std::coroutine_handle::from_promise(promise)} { 67 | } 68 | 69 | ~async_parse() noexcept { 70 | if (handle) { 71 | handle.destroy(); 72 | } 73 | } 74 | 75 | void put(char c) { 76 | handle.promise().input_data = c; 77 | if (!handle.done()) { 78 | handle.resume(); 79 | } 80 | } 81 | 82 | int64_t get() { 83 | if (!handle.done()) { 84 | handle.resume(); 85 | } 86 | return handle.promise().output_data; 87 | } 88 | }; 89 | 90 | async_parse parse_string() { 91 | while (true) { 92 | char c = co_await char{ }; 93 | int64_t number { }; 94 | int64_t sign { 1 }; 95 | 96 | if (c != '-' && c != '+' && !std::isdigit(c)) { 97 | continue; 98 | } 99 | 100 | if (c == '-') { 101 | sign = -1; 102 | } 103 | else if (std::isdigit(c)) { 104 | number = number * 10 + c - '0'; 105 | } 106 | 107 | while (true) { 108 | c = co_await char{}; 109 | if (std::isdigit(c)) { 110 | number = number * 10 + c - '0'; 111 | } 112 | else { 113 | break; 114 | } 115 | } 116 | 117 | co_yield number * sign; 118 | } 119 | } 120 | 121 | int64_t parse_string(const std::string& str) { 122 | int64_t num{ 0 }; 123 | int64_t sign { 1 }; 124 | 125 | std::size_t c = 0; 126 | while (c < str.size()) { 127 | if (str[c] == '-') { 128 | sign = -1; 129 | } 130 | else if (std::isdigit(str[c])) { 131 | num = num * 10ll + (str[c] - '0'); 132 | } 133 | else if (str[c] == '#') { 134 | break; 135 | } 136 | ++c; 137 | } 138 | 139 | return num * sign; 140 | } 141 | 142 | int main() { 143 | std::string num1 = "-123454321#"s; 144 | std::string num2 = "-123454321#98765#-20241337#+31415#"s; 145 | 146 | // parse full string 147 | std::cout << parse_string(num1) << std::endl; 148 | 149 | // parse the first half of the string 150 | std::size_t sz = num1.size(); 151 | async_parse num_parser = parse_string(); 152 | for (std::size_t i = 0; i < sz / 2; ++i) { 153 | num_parser.put(num1[i]); 154 | } 155 | 156 | std::cout << "parsing number...\n"s; 157 | 158 | // parse the second half of the string 159 | for (std::size_t i = sz / 2; i < sz; ++i) { 160 | num_parser.put(num1[i]); 161 | } 162 | 163 | // print the parsed number 164 | std::cout << num_parser.get() << std::endl; 165 | 166 | // now let's parse a string containing several numbers 167 | for (char c : num2) { 168 | num_parser.put(c); 169 | 170 | // do something... 171 | 172 | std::this_thread::sleep_for(1s); 173 | 174 | if (c == '#') { 175 | std::cout << num_parser.get() << std::endl; 176 | } 177 | } 178 | 179 | return 0; 180 | } -------------------------------------------------------------------------------- /Chapter_08/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Output directory 2 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BINARY_DIR}/Chapter_08) 3 | 4 | # Set C++ standard 5 | set(CMAKE_CXX_STANDARD 23) 6 | set(CMAKE_CXX_STANDARD_REQUIRED True) 7 | 8 | # Create executable for each cpp file 9 | file(GLOB SOURCES "*.cpp") 10 | foreach(SOURCE ${SOURCES}) 11 | get_filename_component(EXECUTABLE_NAME ${SOURCE} NAME_WE) 12 | add_executable(${EXECUTABLE_NAME} ${SOURCE}) 13 | target_link_libraries(${EXECUTABLE_NAME}) 14 | endforeach() 15 | -------------------------------------------------------------------------------- /Chapter_09/9x01-io_context_run.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void on_timeout(const boost::system::error_code& ec) { 5 | if (!ec) { 6 | std::cout << "Timer expired.\n"; 7 | } else { 8 | std::cerr << "Error: " << ec.message() << '\n'; 9 | } 10 | } 11 | 12 | int main() { 13 | boost::asio::io_context io_context; 14 | boost::asio::steady_timer timer(io_context, std::chrono::seconds(3)); 15 | timer.async_wait(&on_timeout); 16 | io_context.run(); 17 | return 0; 18 | } 19 | -------------------------------------------------------------------------------- /Chapter_09/9x02-executor_work_guard.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std::chrono_literals; 7 | 8 | void background_task(boost::asio::io_context& io_context) { 9 | // Waiting for 2 seconds before posting work 10 | std::this_thread::sleep_for(2s); 11 | std::cout << "Posting a background task.\n"; 12 | io_context.post([]() { std::cout << "Background task completed!\n"; }); 13 | } 14 | 15 | int main() { 16 | boost::asio::io_context io_context; 17 | auto work_guard = boost::asio::make_work_guard(io_context); 18 | 19 | // Work guard avoids run() to return immediately 20 | std::thread io_thread([&io_context]() { 21 | std::cout << "Running io_context.\n"; 22 | io_context.run(); 23 | std::cout << "io_context stopped.\n"; 24 | }); 25 | 26 | // Creating a thread and posting some work after 2 seconds. 27 | std::thread worker(background_task, std::ref(io_context)); 28 | 29 | // Main thread doing some work. 30 | std::this_thread::sleep_for(5s); 31 | 32 | // Removing work guard to let the io_context stop. 33 | std::cout << "Removing work_guard.\n"; 34 | work_guard.reset(); 35 | 36 | // Joining threads 37 | worker.join(); 38 | io_thread.join(); 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /Chapter_09/9x03-chained_asynchronous_tasks.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std::chrono_literals; 7 | 8 | int main() { 9 | boost::asio::io_context io_context; 10 | boost::asio::steady_timer timer(io_context, 3s); 11 | 12 | std::function timer_handler; 13 | timer_handler = [&timer, &timer_handler](const boost::system::error_code& ec) { 14 | if (!ec) { 15 | std::cout << "Handler: Timer expired." << std::endl; 16 | timer.expires_after(1s); 17 | timer.async_wait(timer_handler); 18 | } else { 19 | std::cerr << "Handler error: " << ec.message() << std::endl; 20 | } 21 | }; 22 | timer.async_wait(timer_handler); 23 | io_context.run(); 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /Chapter_09/9x04-post_and_dispatch.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | boost::asio::io_context io_context; 6 | 7 | io_context.post([] { std::cout << "This will always run asynchronously.\n"; }); 8 | 9 | io_context.dispatch([] { std::cout << "This might run immediately or be queued.\n"; }); 10 | 11 | io_context.run(); 12 | return 0; 13 | } 14 | -------------------------------------------------------------------------------- /Chapter_09/9x05-future_exception_handling.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std::chrono_literals; 8 | 9 | int main() { 10 | boost::asio::io_context io_context; 11 | boost::asio::steady_timer timer(io_context, 1s); 12 | 13 | auto fut = timer.async_wait(boost::asio::use_future); 14 | 15 | std::thread io_thread([&io_context]() { io_context.run(); }); 16 | 17 | std::this_thread::sleep_for(3s); 18 | 19 | // Cancel timer, throwing an exception that is caught by the future. 20 | timer.cancel(); 21 | 22 | try { 23 | fut.get(); 24 | std::cout << "Timer expired successfully!\n"; 25 | } catch (const boost::system::system_error& e) { 26 | std::cout << "Timer failed: " << e.code().message() << '\n'; 27 | } 28 | 29 | io_thread.join(); 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /Chapter_09/9x06-exception_handling.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | void my_handler() { 6 | try { 7 | std::cout << "Starting work in handler...\n"; 8 | throw std::runtime_error("An error occurred during the operation"); 9 | std::cout << "Handler completed work successfully.\n"; 10 | } catch (const std::exception& e) { 11 | std::cerr << "Caught exception: " << e.what() << std::endl; 12 | } 13 | } 14 | 15 | void post_handler(boost::asio::io_context& io_context) { 16 | io_context.post(my_handler); 17 | } 18 | 19 | int main() { 20 | boost::asio::io_context io_context; 21 | post_handler(io_context); 22 | io_context.run(); 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /Chapter_09/9x07-single_threaded.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std::chrono_literals; 6 | 7 | void handle_timer_expiry(const boost::system::error_code& ec) { 8 | if (!ec) { 9 | std::cout << "Timer expired!\n"; 10 | } else { 11 | std::cerr << "Error in timer: " << ec.message() << std::endl; 12 | } 13 | } 14 | 15 | int main() { 16 | boost::asio::io_context io_context; 17 | boost::asio::steady_timer timer(io_context, std::chrono::seconds(1)); 18 | timer.async_wait(&handle_timer_expiry); 19 | io_context.run(); 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /Chapter_09/9x08-multithreaded_io_context_main_thread.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | void long_running_task(boost::asio::io_context& io_context, int task_duration) { 6 | std::cout << "Background task started: Duration = " << task_duration << " seconds.\n"; 7 | std::this_thread::sleep_for(std::chrono::seconds(task_duration)); 8 | io_context.post([&io_context]() { 9 | std::cout << "Background task completed.\n"; 10 | io_context.stop(); 11 | }); 12 | } 13 | 14 | int main() { 15 | boost::asio::io_context io_context; 16 | 17 | auto work_guard = boost::asio::make_work_guard(io_context); 18 | 19 | io_context.post([&io_context]() { 20 | std::thread t(long_running_task, std::ref(io_context), 2); 21 | std::cout << "Detaching thread" << std::endl; 22 | t.detach(); 23 | }); 24 | 25 | std::cout << "Running io_context...\n"; 26 | io_context.run(); 27 | std::cout << "io_context exit.\n"; 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /Chapter_09/9x09-multithreaded_io_context_own_thread.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define sync_cout std::osyncstream(std::cout) 8 | 9 | using namespace std::chrono_literals; 10 | 11 | void background_task(int i) { 12 | sync_cout << "Thread " << i << ": Starting...\n"; 13 | boost::asio::io_context io_context; 14 | auto work_guard = boost::asio::make_work_guard(io_context); 15 | 16 | sync_cout << "Thread " << i << ": Setup timer...\n"; 17 | boost::asio::steady_timer timer(io_context, 1s); 18 | timer.async_wait([&](const boost::system::error_code& ec) { 19 | if (!ec) { 20 | sync_cout << "Timer expired successfully!\n"; 21 | } else { 22 | sync_cout << "Timer error: " << ec.message() << '\n'; 23 | } 24 | work_guard.reset(); 25 | }); 26 | 27 | sync_cout << "Thread " << i << ": Running io_context...\n"; 28 | io_context.run(); 29 | } 30 | 31 | int main() { 32 | const int num_threads = 4; 33 | std::vector threads; 34 | 35 | for (auto i = 0; i < num_threads; ++i) { 36 | threads.emplace_back(background_task, i); 37 | } 38 | 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /Chapter_09/9x10-single_io_context_several_handlers_thread.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define sync_cout std::osyncstream(std::cout) 9 | 10 | using namespace std::chrono_literals; 11 | 12 | void background_task(int task_id) { 13 | boost::asio::post([task_id]() { 14 | sync_cout << "Task " << task_id << " is being handled in thread " << std::this_thread::get_id() << std::endl; 15 | std::this_thread::sleep_for(2s); 16 | sync_cout << "Task " << task_id << " complete.\n"; 17 | }); 18 | } 19 | 20 | int main() { 21 | boost::asio::io_context io_context; 22 | auto work_guard = boost::asio::make_work_guard(io_context); 23 | 24 | std::jthread io_context_thread([&io_context]() { io_context.run(); }); 25 | 26 | const int num_threads = 4; 27 | std::vector threads; 28 | for (int i = 0; i < num_threads; ++i) { 29 | background_task(i); 30 | } 31 | 32 | std::this_thread::sleep_for(5s); 33 | work_guard.reset(); 34 | 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /Chapter_09/9x11-multithreaded_single_io_context_run_different_threads.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std::chrono_literals; 7 | 8 | int main() { 9 | boost::asio::io_context io_context; 10 | 11 | boost::asio::steady_timer timer(io_context, 2s); 12 | timer.async_wait([](const boost::system::error_code& /*ec*/) { 13 | std::cout << "Timer expired!\n"; 14 | }); 15 | 16 | const std::size_t num_threads = std::thread::hardware_concurrency(); 17 | std::vector threads; 18 | for (std::size_t i = 0; i < num_threads; ++i) { 19 | threads.emplace_back([&io_context]() { 20 | io_context.run(); 21 | }); 22 | } 23 | 24 | for (auto& t : threads) { 25 | t.join(); 26 | } 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /Chapter_09/9x12-objects_lifetime.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | constexpr int port = 1234; 6 | 7 | using boost::asio::ip::tcp; 8 | 9 | class Session : public std::enable_shared_from_this { 10 | public: 11 | Session(tcp::socket socket) : socket_(std::move(socket)) {} 12 | 13 | void start() { do_read(); } 14 | 15 | private: 16 | static const size_t max_length = 1024; 17 | 18 | void do_read(); 19 | void do_write(std::size_t length); 20 | 21 | tcp::socket socket_; 22 | char data_[max_length]; 23 | }; 24 | 25 | class EchoServer { 26 | public: 27 | EchoServer(boost::asio::io_context& io_context, short port) 28 | : acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) { 29 | do_accept(); 30 | } 31 | 32 | private: 33 | void do_accept() { 34 | acceptor_.async_accept([this](boost::system::error_code ec, tcp::socket socket) { 35 | if (!ec) { 36 | std::make_shared(std::move(socket))->start(); 37 | } 38 | do_accept(); 39 | }); 40 | } 41 | 42 | tcp::acceptor acceptor_; 43 | }; 44 | 45 | int main() { 46 | try { 47 | boost::asio::io_context io_context; 48 | EchoServer server(io_context, port); 49 | io_context.run(); 50 | } catch (std::exception& e) { 51 | std::cerr << "Exception: " << e.what() << "\n"; 52 | } 53 | 54 | return 0; 55 | } 56 | 57 | 58 | void Session::do_read() { 59 | auto self(shared_from_this()); 60 | socket_.async_read_some(boost::asio::buffer(data_, max_length), 61 | [this, self](boost::system::error_code ec, std::size_t length) { 62 | if (!ec) { 63 | do_write(length); 64 | } 65 | }); 66 | } 67 | 68 | void Session::do_write(std::size_t length) { 69 | auto self(shared_from_this()); 70 | boost::asio::async_write(socket_, boost::asio::buffer(data_, length), 71 | [this, self](boost::system::error_code ec, std::size_t /*length*/) { 72 | if (!ec) { 73 | do_read(); 74 | } 75 | }); 76 | } -------------------------------------------------------------------------------- /Chapter_09/9x13-scatter_gather.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using boost::asio::ip::tcp; 7 | 8 | constexpr int port = 1234; 9 | 10 | void handle_client(tcp::socket& socket) { 11 | const size_t size_buffer = 5; 12 | boost::asio::streambuf buf1, buf2; 13 | 14 | std::array buffers = { 15 | buf1.prepare(size_buffer), 16 | buf2.prepare(size_buffer) 17 | }; 18 | 19 | boost::system::error_code ec; 20 | size_t bytes_recv = socket.read_some(buffers, ec); 21 | if (ec) { 22 | std::cerr << "Error on receive: " << ec.message() << '\n'; 23 | return; 24 | } 25 | std::cout << "Received " << bytes_recv << " bytes\n"; 26 | 27 | // Extract data from buffers 28 | buf1.commit(5); 29 | buf2.commit(5); 30 | 31 | std::istream is1(&buf1); 32 | std::istream is2(&buf2); 33 | 34 | std::string data1, data2; 35 | is1 >> data1; 36 | is2 >> data2; 37 | 38 | std::cout << "Buffer 1: " << data1 << std::endl; 39 | std::cout << "Buffer 2: " << data2 << std::endl; 40 | } 41 | 42 | int main() { 43 | try { 44 | boost::asio::io_context io_context; 45 | 46 | tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), port)); 47 | std::cout << "Server is running on port " << port << "...\n"; 48 | 49 | while (true) { 50 | tcp::socket socket(io_context); 51 | acceptor.accept(socket); 52 | std::cout << "Client connected...\n"; 53 | 54 | handle_client(socket); 55 | std::cout << "Client disconnected...\n"; 56 | } 57 | } catch (std::exception& e) { 58 | std::cerr << "Exception: " << e.what() << '\n'; 59 | } 60 | 61 | return 0; 62 | } 63 | -------------------------------------------------------------------------------- /Chapter_09/9x14-signal_handling.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | try { 6 | boost::asio::io_context io_context; 7 | boost::asio::signal_set signals(io_context, SIGINT, SIGTERM); 8 | 9 | auto handle_signal = [&](const boost::system::error_code& ec, int signal) { 10 | if (!ec) { 11 | std::cout << "Signal received: " << signal << std::endl; 12 | 13 | // Code to perform cleanup or shutdown. 14 | io_context.stop(); 15 | } 16 | }; 17 | 18 | signals.async_wait(handle_signal); 19 | std::cout << "Application is running. Press Ctrl+C to stop...\n"; 20 | 21 | io_context.run(); 22 | std::cout << "Application has exited cleanly.\n"; 23 | } catch (std::exception& e) { 24 | std::cerr << "Exception: " << e.what() << '\n'; 25 | } 26 | 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /Chapter_09/9x15-canceling_operations.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std::chrono_literals; 7 | 8 | void handle_timeout(const boost::system::error_code& ec) { 9 | if (ec == boost::asio::error::operation_aborted) { 10 | std::cout << "Timer canceled.\n"; 11 | } else if (!ec) { 12 | std::cout << "Timer expired.\n"; 13 | } else { 14 | std::cout << "Error: " << ec.message() << std::endl; 15 | } 16 | } 17 | 18 | int main() { 19 | boost::asio::io_context io_context; 20 | boost::asio::steady_timer timer(io_context, 5s); 21 | 22 | timer.async_wait(handle_timeout); 23 | 24 | std::this_thread::sleep_for(2s); 25 | timer.cancel(); 26 | 27 | io_context.run(); 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /Chapter_09/9x16-cancellation_slot.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std::chrono_literals; 7 | 8 | void handle_timeout(const boost::system::error_code& ec) { 9 | if (ec == boost::asio::error::operation_aborted) { 10 | std::cout << "Timer canceled.\n"; 11 | } else if (!ec) { 12 | std::cout << "Timer expired.\n"; 13 | } else { 14 | std::cout << "Error: " << ec.message() << std::endl; 15 | } 16 | } 17 | 18 | int main() { 19 | boost::asio::io_context io_context; 20 | boost::asio::steady_timer timer(io_context, 5s); 21 | 22 | boost::asio::cancellation_signal cancel_signal; 23 | 24 | timer.async_wait(boost::asio::bind_cancellation_slot( 25 | cancel_signal.slot(), 26 | handle_timeout 27 | )); 28 | 29 | std::this_thread::sleep_for(2s); 30 | cancel_signal.emit(boost::asio::cancellation_type::all); 31 | 32 | io_context.run(); 33 | return 0; 34 | } 35 | -------------------------------------------------------------------------------- /Chapter_09/9x17-implicit_strands.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std::chrono_literals; 6 | 7 | void handle_timer_expiry(boost::asio::steady_timer& timer, int count) { 8 | std::cout << "Timer expired. Count: " << count << std::endl; 9 | timer.expires_after(1s); 10 | timer.async_wait([&timer, count](const boost::system::error_code& ec) { 11 | if (!ec) { 12 | handle_timer_expiry(timer, count + 1); 13 | } else { 14 | std::cerr << "Error: " << ec.message() << std::endl; 15 | } 16 | }); 17 | } 18 | 19 | int main() { 20 | boost::asio::io_context io_context; 21 | boost::asio::steady_timer timer(io_context, 1s); 22 | int count = 0; 23 | timer.async_wait([&](const boost::system::error_code& ec) { 24 | if (!ec) { 25 | handle_timer_expiry(timer, count); 26 | } else { 27 | std::cerr << "Error: " << ec.message() << std::endl; 28 | } 29 | }); 30 | io_context.run(); 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /Chapter_09/9x18-explicit_strands.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std::chrono_literals; 11 | 12 | const std::string log_filename = "log.txt"; 13 | const unsigned num_threads = 4; 14 | const unsigned num_messages_per_thread = 5; 15 | 16 | class Logger { 17 | public: 18 | Logger(boost::asio::io_context& io_context, const std::string& filename) 19 | : strand_(io_context), file_(filename, std::ios::out | std::ios::app) 20 | { 21 | if (!file_.is_open()) { 22 | throw std::runtime_error("Failed to open log file"); 23 | } 24 | } 25 | 26 | void log(const std::string message) { 27 | //strand_.post(std::bind(&Logger::do_log, this, message)); 28 | strand_.post([this, message]() { do_log(message); }); 29 | } 30 | 31 | private: 32 | void do_log(const std::string message) { 33 | file_ << message << std::endl; 34 | } 35 | 36 | boost::asio::io_context::strand strand_; 37 | std::ofstream file_; 38 | }; 39 | 40 | void worker(std::shared_ptr logger, int id) { 41 | for (unsigned i = 0; i < num_messages_per_thread; ++i) { 42 | std::ostringstream oss; 43 | oss << "Thread " << id << " logging message " << i; 44 | logger->log(oss.str()); 45 | std::this_thread::sleep_for(100ms); 46 | } 47 | } 48 | 49 | int main() { 50 | try { 51 | boost::asio::io_context io_context; 52 | auto work_guard = boost::asio::make_work_guard(io_context); 53 | std::shared_ptr logger = std::make_shared(io_context, log_filename); 54 | 55 | std::cout << "Logging " << num_messages_per_thread << " messages from " << num_threads << " threads\n"; 56 | 57 | std::vector threads; 58 | for (unsigned i = 0; i < num_threads; ++i) { 59 | threads.emplace_back(worker, logger, i); 60 | } 61 | 62 | threads.emplace_back([&]() { io_context.run_for(2s); }); 63 | 64 | } catch (std::exception& e) { 65 | std::cerr << "Exception: " << e.what() << std::endl; 66 | } 67 | 68 | std::cout << "Done!" << std::endl; 69 | return 0; 70 | } 71 | -------------------------------------------------------------------------------- /Chapter_09/9x19-coroutines.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using boost::asio::ip::tcp; 7 | 8 | boost::asio::awaitable echo(tcp::socket socket) { 9 | char data[1024]; 10 | 11 | while (true) { 12 | std::cout << "Reading data from socket...\n"; 13 | std::size_t bytes_read = co_await socket.async_read_some(boost::asio::buffer(data), boost::asio::use_awaitable); 14 | 15 | if (bytes_read == 0) { 16 | std::cout << "No data. Exiting loop...\n"; 17 | break; 18 | } 19 | 20 | // Remove /r/n from end of received data 21 | std::string str(data, bytes_read); 22 | if (!str.empty() && str.back() == '\n') { 23 | str.pop_back(); 24 | } 25 | if (!str.empty() && str.back() == '\r') { 26 | str.pop_back(); 27 | } 28 | 29 | // If the client send QUIT, the server closes the connection 30 | if (str == "QUIT") { 31 | std::string bye("Good bye!\n"); 32 | co_await boost::asio::async_write(socket, boost::asio::buffer(bye), boost::asio::use_awaitable); 33 | break; 34 | } 35 | 36 | std::cout << "Writing '" << str << "' back into the socket...\n"; 37 | co_await boost::asio::async_write(socket, boost::asio::buffer(data, bytes_read), boost::asio::use_awaitable); 38 | } 39 | } 40 | 41 | boost::asio::awaitable listener(boost::asio::io_context& io_context, unsigned short port) { 42 | tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), port)); 43 | 44 | while (true) { 45 | std::cout << "Accepting connections...\n"; 46 | tcp::socket socket = co_await acceptor.async_accept(boost::asio::use_awaitable); 47 | 48 | std::cout << "Starting an Echo connection handler...\n"; 49 | boost::asio::co_spawn(io_context, echo(std::move(socket)), boost::asio::detached); 50 | } 51 | } 52 | 53 | int main() { 54 | boost::asio::io_context io_context; 55 | 56 | try { 57 | boost::asio::co_spawn(io_context, listener(io_context, 12345), boost::asio::detached); 58 | io_context.run(); 59 | 60 | } catch (std::exception& e) { 61 | std::cerr << "Error: " << e.what() << std::endl; 62 | io_context.stop(); 63 | } 64 | 65 | return 0; 66 | } 67 | -------------------------------------------------------------------------------- /Chapter_09/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Output directory 2 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BINARY_DIR}/Chapter_09) 3 | 4 | # Create executable for each cpp file 5 | file(GLOB SOURCES "*.cpp") 6 | foreach(SOURCE ${SOURCES}) 7 | get_filename_component(EXECUTABLE_NAME ${SOURCE} NAME_WE) 8 | add_executable(${EXECUTABLE_NAME} ${SOURCE}) 9 | target_link_libraries(${EXECUTABLE_NAME} ${Boost_LIBRARIES}) 10 | endforeach() 11 | -------------------------------------------------------------------------------- /Chapter_10/10x01-hello_boost_cobalt.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | boost::cobalt::main co_main(int, char*[]) { 6 | std::cout << "Hello Boost.Cobalt\n"; 7 | co_return 0; 8 | } -------------------------------------------------------------------------------- /Chapter_10/10x02-basic_generator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | using namespace std::chrono_literals; 7 | 8 | boost::cobalt::generator basic_generator() 9 | { 10 | std::this_thread::sleep_for(1s); 11 | co_yield 1; 12 | std::this_thread::sleep_for(1s); 13 | co_return 0; 14 | } 15 | 16 | boost::cobalt::main co_main(int, char*[]) { 17 | auto g = basic_generator(); 18 | 19 | std::cout << co_await g << std::endl; 20 | std::cout << co_await g << std::endl; 21 | 22 | co_return 0; 23 | } -------------------------------------------------------------------------------- /Chapter_10/10x03-input_output_generator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | boost::cobalt::generator square_generator(int x) 6 | { 7 | while (x != 0) { 8 | x = co_yield x * x; 9 | } 10 | 11 | co_return 0; 12 | } 13 | 14 | boost::cobalt::main co_main(int, char*[]) { 15 | auto g = square_generator(10); 16 | 17 | std::cout << co_await g(4) << std::endl; 18 | std::cout << co_await g(12) << std::endl; 19 | std::cout << co_await g(0) << std::endl; 20 | 21 | co_return 0; 22 | } -------------------------------------------------------------------------------- /Chapter_10/10x04-input_output_lazy_generator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | boost::cobalt::generator square_generator() { 6 | auto x = co_await boost::cobalt::this_coro::initial; 7 | while (x != 0) { 8 | x = co_yield x * x; 9 | } 10 | 11 | co_return 0; 12 | } 13 | 14 | boost::cobalt::main co_main(int, char*[]) { 15 | auto g = square_generator(); 16 | 17 | std::cout << co_await g(4) << std::endl; 18 | std::cout << co_await g(10) << std::endl; 19 | std::cout << co_await g(12) << std::endl; 20 | std::cout << co_await g(0) << std::endl; 21 | 22 | co_return 0; 23 | } -------------------------------------------------------------------------------- /Chapter_10/10x05-fibonacci_generator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | using namespace std::chrono_literals; 7 | 8 | boost::cobalt::generator fibonacci_term() { 9 | auto fibonacci = [](int n) { 10 | if (n < 2) { 11 | return n; 12 | } 13 | 14 | int f0 = 0; 15 | int f1 = 1; 16 | int f; 17 | 18 | for (int i = 2; i <= n; ++i) { 19 | f = f0 + f1; 20 | f0 = f1; 21 | f1 = f; 22 | } 23 | 24 | return f; 25 | }; 26 | 27 | auto x = co_await boost::cobalt::this_coro::initial; 28 | while (x != -1) { 29 | x = co_yield fibonacci(x); 30 | } 31 | 32 | co_return 0; 33 | } 34 | 35 | boost::cobalt::generator fibonacci_sequence() { 36 | int f0 = 0; 37 | int f1 = 1; 38 | int f = 0; 39 | 40 | while (true) { 41 | co_yield f0; 42 | 43 | f = f0 + f1; 44 | f0 = f1; 45 | f1 = f; 46 | } 47 | } 48 | 49 | boost::cobalt::main co_main(int, char*[]) { 50 | auto g = fibonacci_sequence(); 51 | 52 | // print first 10 fibonacci numbers 53 | for (int i = 0; i < 10; ++i) { 54 | std::cout << co_await g << std::endl; 55 | } 56 | 57 | // do something... 58 | std::this_thread::sleep_for(2s); 59 | 60 | // print 10 more fibonacci numbers 61 | for (int i = 0; i < 10; ++i) { 62 | std::cout << co_await g << std::endl; 63 | } 64 | 65 | // print first 10 even fibonacci numbers 66 | auto h = fibonacci_term(); 67 | for (int i = 0; i < 20; i += 2) { 68 | std::cout << i << " " << co_await h(i) << std::endl; 69 | } 70 | 71 | co_return 0; 72 | } 73 | -------------------------------------------------------------------------------- /Chapter_10/10x06-simple_promise.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | boost::cobalt::promise random_number(int min, int max) { 8 | std::random_device rd; 9 | std::mt19937 gen(rd()); 10 | std::uniform_int_distribution<> dist(min, max); 11 | co_return dist(gen); 12 | } 13 | 14 | boost::cobalt::promise random(int min, int max) { 15 | int res = co_await random_number(min, max); 16 | co_return res; 17 | } 18 | 19 | boost::cobalt::promise> random_vector(int min, int max, int n) { 20 | std::vector rv(n); 21 | for (int i = 0; i < n; ++i) { 22 | rv[i] = co_await random_number(min, max); 23 | } 24 | co_return rv; 25 | } 26 | 27 | boost::cobalt::main co_main(int, char*[]) { 28 | for (int i = 0; i < 10; ++i) { 29 | auto r = random(1, 100); 30 | std::cout << "random number between 1 and 100: " << co_await r << std::endl; 31 | } 32 | 33 | auto v = random_vector(1, 100, 20); 34 | for (int n : v.get()) { 35 | std::cout << n << " "; 36 | } 37 | std::cout << std::endl; 38 | 39 | co_return 0; 40 | } -------------------------------------------------------------------------------- /Chapter_10/10x07-promise_task_difference.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | void sleep() { 8 | std::this_thread::sleep_for(std::chrono::seconds(2)); 9 | } 10 | 11 | boost::cobalt::promise eager_promise() { 12 | std::cout << "Eager promise started\n"; 13 | sleep(); 14 | std::cout << "Eager promise done\n"; 15 | co_return 1; 16 | } 17 | 18 | boost::cobalt::task lazy_task() { 19 | std::cout << "Lazy task started\n"; 20 | sleep(); 21 | std::cout << "Lazy task done\n"; 22 | co_return 2; 23 | } 24 | 25 | boost::cobalt::main co_main(int, char*[]) { 26 | std::cout << "Calling eager_promise...\n"; 27 | auto promise_result = eager_promise(); 28 | std::cout << "Promise called, but not yet awaited.\n"; 29 | 30 | std::cout << "Calling lazy_task...\n"; 31 | auto task_result = lazy_task(); 32 | std::cout << "Task called, but not yet awaited.\n"; 33 | 34 | std::cout << "Awaiting both results...\n"; 35 | int promise_value = co_await promise_result; 36 | std::cout << "Promise value: " << promise_value 37 | << std::endl; 38 | 39 | int task_value = co_await task_result; 40 | std::cout << "Task value: " << task_value 41 | << std::endl; 42 | 43 | co_return 0; 44 | } -------------------------------------------------------------------------------- /Chapter_10/10x08-channels.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | boost::cobalt::promise producer(boost::cobalt::channel& ch) { 6 | for (int i = 1; i <= 10; ++i) { 7 | std::cout << "Producer waiting for request\n"; 8 | co_await ch.write(i); 9 | std::cout << "Producing value " << i << std::endl; 10 | } 11 | std::cout << "Producer end\n"; 12 | ch.close(); 13 | co_return; 14 | } 15 | 16 | boost::cobalt::main co_main(int, char*[]) { 17 | boost::cobalt::channel ch; 18 | 19 | auto p = producer(ch); 20 | while (ch.is_open()) { 21 | std::cout << "Consumer waiting for next number \n"; 22 | std::this_thread::sleep_for(std::chrono::seconds(5)); 23 | auto n = co_await ch.read(); 24 | std::cout << "Consuming value " << n << std::endl; 25 | std::cout << n * n << std::endl; 26 | } 27 | 28 | co_await p; 29 | co_return 0; 30 | } 31 | -------------------------------------------------------------------------------- /Chapter_10/10x09-cobalt_join_gather.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | using namespace std::chrono_literals; 9 | 10 | boost::cobalt::promise process(std::chrono::milliseconds ms) { 11 | if (ms > std::chrono::milliseconds(5000)) { 12 | throw std::runtime_error("delay throw"); 13 | } 14 | 15 | boost::asio::steady_timer tmr{ co_await boost::cobalt::this_coro::executor, ms }; 16 | co_await tmr.async_wait(boost::cobalt::use_op); 17 | co_return ms.count(); 18 | } 19 | 20 | boost::cobalt::main co_main(int, char*[]) { 21 | auto result = co_await boost::cobalt::join(process(100ms), 22 | process(200ms), 23 | process(300ms)); 24 | 25 | std::cout << "First coroutine finished in: " 26 | << std::get<0>(result) << "ms\n"; 27 | std::cout << "Second coroutine took finished in: " 28 | << std::get<1>(result) << "ms\n"; 29 | std::cout << "Third coroutine took finished in: " 30 | << std::get<2>(result) << "ms\n"; 31 | 32 | try { 33 | auto result_throw = co_await boost::cobalt::join(process(100ms), 34 | process(20000ms), 35 | process(300ms)); 36 | } 37 | catch (...) { 38 | std::cout << "An exception was thrown\n"; 39 | } 40 | 41 | try { 42 | auto result_throw = co_await boost::cobalt::gather(process(100ms), 43 | process(20000ms), 44 | process(300ms)); 45 | 46 | 47 | if (std::get<0>(result_throw).has_value()) { 48 | std::cout << "First coroutine took: " 49 | << *std::get<0>(result_throw) << "msec\n"; 50 | } 51 | else { 52 | std::cout << "First coroutine threw an exception\n"; 53 | } 54 | if (std::get<1>(result_throw).has_value()) { 55 | std::cout << "Second coroutine took: " 56 | << *std::get<1>(result_throw) << "msec\n"; 57 | } 58 | else { 59 | std::cout << "Second coroutine threw an exception\n"; 60 | } 61 | if (std::get<2>(result_throw).has_value()) { 62 | std::cout << "Third coroutine took: " 63 | << *std::get<2>(result_throw) << "msec\n"; 64 | } 65 | else { 66 | std::cout << "Third coroutine threw an exception\n"; 67 | } 68 | } 69 | catch (...) { 70 | // this is never reached because gather doesn't throw exceptions 71 | std::cout << "An exception was thrown\n"; 72 | } 73 | 74 | co_return 0; 75 | } -------------------------------------------------------------------------------- /Chapter_10/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Output directory 2 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BINARY_DIR}/Chapter_10) 3 | 4 | # Set C++ standard 5 | set(CMAKE_CXX_STANDARD 20) 6 | set(CMAKE_CXX_STANDARD_REQUIRED True) 7 | 8 | # Include Boost::Cobalt 9 | find_package(Boost 1.86.0 REQUIRED COMPONENTS thread system container cobalt) 10 | if(NOT Boost_FOUND) 11 | message(FATAL_ERROR "Boost 1.86.0 with Boost.Cobalt is required but not found.") 12 | endif() 13 | 14 | # Include Boost directories 15 | include_directories(${Boost_INCLUDE_DIRS}) 16 | 17 | # Create executable for each cpp file 18 | file(GLOB SOURCES "*.cpp") 19 | foreach(SOURCE ${SOURCES}) 20 | get_filename_component(EXECUTABLE_NAME ${SOURCE} NAME_WE) 21 | add_executable(${EXECUTABLE_NAME} ${SOURCE}) 22 | target_link_libraries(${EXECUTABLE_NAME} ${Boost_LIBRARIES}) 23 | endforeach() 24 | -------------------------------------------------------------------------------- /Chapter_11/11x01-logging.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std::chrono_literals; 13 | 14 | int main() { 15 | uint32_t counter1{}; 16 | std::mutex mtx1; 17 | 18 | uint32_t counter2{}; 19 | std::mutex mtx2; 20 | 21 | // Create a multi-sink logger logging into console and a file simultaneously 22 | auto console_sink = std::make_shared(); 23 | console_sink->set_level(spdlog::level::debug); 24 | 25 | auto file_sink = std::make_shared("logging.log", true); 26 | file_sink->set_level(spdlog::level::info); 27 | 28 | spdlog::logger logger("multi_sink", {console_sink, file_sink}); 29 | logger.set_pattern("%Y-%m-%d %H:%M:%S.%f - Thread %t [%l] : %v"); 30 | logger.set_level(spdlog::level::debug); 31 | 32 | auto increase_and_swap = [&]() { 33 | logger.info("Incrementing both counters..."); 34 | counter1++; 35 | counter2++; 36 | 37 | logger.info("Swapping counters..."); 38 | std::swap(counter1, counter2); 39 | }; 40 | 41 | auto worker1 = [&]() { 42 | logger.debug("Entering worker1"); 43 | 44 | logger.info("Locking mtx1..."); 45 | std::lock_guard lock1(mtx1); 46 | logger.info("Mutex mtx1 locked"); 47 | 48 | std::this_thread::sleep_for(100ms); 49 | 50 | logger.info("Locking mtx2..."); 51 | std::lock_guard lock2(mtx2); 52 | logger.info("Mutex mtx2 locked"); 53 | 54 | increase_and_swap(); 55 | 56 | logger.debug("Leaving worker1"); 57 | }; 58 | 59 | auto worker2 = [&]() { 60 | logger.debug("Entering worker2"); 61 | 62 | logger.info("Locking mtx2..."); 63 | std::lock_guard lock2(mtx2); 64 | logger.info("Mutex mtx2 locked"); 65 | 66 | std::this_thread::sleep_for(100ms); 67 | 68 | logger.info("Locking mtx1..."); 69 | std::lock_guard lock1(mtx1); 70 | logger.info("Mutex mtx1 locked"); 71 | 72 | increase_and_swap(); 73 | 74 | logger.debug("Leaving worker2"); 75 | }; 76 | 77 | logger.debug("Starting main function..."); 78 | 79 | std::thread t1(worker1); 80 | std::thread t2(worker2); 81 | 82 | t1.join(); 83 | t2.join(); 84 | 85 | spdlog::dump_backtrace(); 86 | return 0; 87 | } 88 | -------------------------------------------------------------------------------- /Chapter_11/11x02-debug_deadlock.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std::chrono_literals; 6 | 7 | int main() { 8 | std::mutex mtx1, mtx2; 9 | 10 | std::thread t1([&]() { 11 | std::lock_guard lock1(mtx1); 12 | std::this_thread::sleep_for(100ms); 13 | std::lock_guard lock2(mtx2); 14 | }); 15 | 16 | std::thread t2([&]() { 17 | std::lock_guard lock2(mtx2); 18 | std::this_thread::sleep_for(100ms); 19 | std::lock_guard lock1(mtx1); 20 | }); 21 | 22 | t1.join(); 23 | t2.join(); 24 | 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /Chapter_11/11x03-debug_race_condition.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std::chrono_literals; 9 | 10 | static int g_value = 0; 11 | static std::mutex g_mutex; 12 | 13 | void func1() { 14 | const std::lock_guard lock(g_mutex); 15 | for (int i = 0; i < 10; ++i) { 16 | int old_value = g_value; 17 | int incr = (rand() % 10); 18 | g_value += incr; 19 | assert(g_value == old_value + incr); 20 | std::this_thread::sleep_for(10ms); 21 | } 22 | } 23 | 24 | void func2() { 25 | for (int i = 0; i < 10; ++i) { 26 | int old_value = g_value; 27 | int incr = (rand() % 10); 28 | g_value += (rand() % 10); 29 | assert(g_value == old_value + incr); 30 | std::this_thread::sleep_for(10ms); 31 | } 32 | } 33 | 34 | int main() { 35 | std::thread t1(func1); 36 | std::thread t2(func2); 37 | 38 | t1.join(); 39 | t2.join(); 40 | 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /Chapter_11/11x04-debug_coroutines.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using boost::asio::ip::tcp; 7 | 8 | boost::asio::awaitable echo(tcp::socket socket) { 9 | char data[1024]; 10 | 11 | while (true) { 12 | std::cout << "Reading data from socket...\n"; 13 | std::size_t bytes_read = co_await socket.async_read_some(boost::asio::buffer(data), boost::asio::use_awaitable); 14 | 15 | if (bytes_read == 0) { 16 | std::cout << "No data. Exiting loop...\n"; 17 | break; 18 | } 19 | 20 | // Remove /r/n from end of received data 21 | std::string str(data, bytes_read); 22 | if (!str.empty() && str.back() == '\n') { 23 | str.pop_back(); 24 | } 25 | if (!str.empty() && str.back() == '\r') { 26 | str.pop_back(); 27 | } 28 | 29 | // If the client send QUIT, the server closes the connection 30 | if (str == "QUIT") { 31 | std::string bye("Good bye!\n"); 32 | co_await boost::asio::async_write(socket, boost::asio::buffer(bye), boost::asio::use_awaitable); 33 | break; 34 | } 35 | 36 | std::cout << "Writing '" << str << "' back into the socket...\n"; 37 | co_await boost::asio::async_write(socket, boost::asio::buffer(data, bytes_read), boost::asio::use_awaitable); 38 | } 39 | } 40 | 41 | boost::asio::awaitable listener(boost::asio::io_context& io_context, unsigned short port) { 42 | tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), port)); 43 | 44 | while (true) { 45 | std::cout << "Accepting connections...\n"; 46 | tcp::socket socket = co_await acceptor.async_accept(boost::asio::use_awaitable); 47 | 48 | std::cout << "Starting an Echo connection handler...\n"; 49 | boost::asio::co_spawn(io_context, echo(std::move(socket)), boost::asio::detached); 50 | } 51 | } 52 | 53 | int main() { 54 | boost::asio::io_context io_context; 55 | 56 | try { 57 | boost::asio::co_spawn(io_context, listener(io_context, 12345), boost::asio::detached); 58 | io_context.run(); 59 | 60 | } catch (std::exception& e) { 61 | std::cerr << "Error: " << e.what() << std::endl; 62 | io_context.stop(); 63 | } 64 | 65 | return 0; 66 | } 67 | -------------------------------------------------------------------------------- /Chapter_11/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Output directory 2 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BINARY_DIR}/Chapter_11) 3 | 4 | # Create executable for each cpp file 5 | file(GLOB SOURCES "*.cpp") 6 | foreach(SOURCE ${SOURCES}) 7 | get_filename_component(EXECUTABLE_NAME ${SOURCE} NAME_WE) 8 | add_executable(${EXECUTABLE_NAME} ${SOURCE}) 9 | target_link_libraries(${EXECUTABLE_NAME} ${Boost_LIBRARIES} fmt::fmt) 10 | endforeach() 11 | -------------------------------------------------------------------------------- /Chapter_12/12x01-ASAN_heap_use_after_free.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | auto arr = new int[100]; 6 | delete[] arr; 7 | std::cout << "arr[0] = " << arr[0] << '\n'; 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /Chapter_12/12x02-ASAN_stack_use_after_return.cpp: -------------------------------------------------------------------------------- 1 | // By default, AddressSanitizer does not try to detect 2 | // stack-use-after-return bugs. 3 | // Need to run with ASAN_OPTIONS=detect_stack_use_after_return=1 4 | 5 | #include 6 | 7 | int *ptr = nullptr; 8 | 9 | __attribute__((noinline)) 10 | void func() { 11 | int local[100]; 12 | // Danger: Pointer to local variable 13 | ptr = &local[0]; 14 | } 15 | 16 | int main() { 17 | func(); 18 | std::cout << "Pointer value: " << *ptr << '\n'; 19 | return 0; 20 | } 21 | -------------------------------------------------------------------------------- /Chapter_12/12x03-ASAN_stack_user_after_scope.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Check can be disabled in run-time by running: 3 | // ASAN_OPTIONS=detect_stack_use_after_scope=0 4 | 5 | volatile int *p = 0; 6 | 7 | int main() { 8 | { 9 | int x = 0; 10 | p = &x; 11 | } 12 | *p = 5; 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /Chapter_12/12x04-ASAN_heap_buffer_overflow.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | auto arr = new int[100]; 5 | arr[0] = 0; 6 | int res = arr[100]; 7 | std::cout << "res = " << res << '\n'; 8 | delete[] arr; 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /Chapter_12/12x05-ASAN_stack_buffer_overflow.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | int arr[100]; 5 | arr[1] = 0; 6 | auto val = arr[100]; 7 | std::cout << "val = " << val << '\n'; 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /Chapter_12/12x06-ASAN_global_buffer_overflow.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int arr[100] = {-1}; 4 | 5 | int main() { 6 | auto val = arr[100]; 7 | std::cout << "val = " << val << '\n'; 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /Chapter_12/12x07-LSAN_missing_delete.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() { 6 | auto arr = new char[100]; 7 | strcpy(arr, "Hello world!"); 8 | std::cout << "String = " << arr << '\n'; 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /Chapter_12/12x08-TSAN_data_race_global_variable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int globalVar{0}; 4 | 5 | void increase() { 6 | globalVar++; 7 | } 8 | 9 | void decrease() { 10 | globalVar--; 11 | } 12 | 13 | int main() { 14 | std::thread t1(increase); 15 | std::thread t2(decrease); 16 | 17 | t1.join(); 18 | t2.join(); 19 | return 0; 20 | } 21 | -------------------------------------------------------------------------------- /Chapter_12/12x09-TSAN_data_race_stl_map.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | std::map m; 5 | 6 | void Thread1() { 7 | m[123] = 1; 8 | } 9 | 10 | void Thread2() { 11 | m[345] = 0; 12 | } 13 | 14 | int main() { 15 | std::jthread t1(Thread1); 16 | std::jthread t2(Thread2); 17 | return 0; 18 | } 19 | -------------------------------------------------------------------------------- /Chapter_12/12x10-TSAN_data_race_stl_map_pointer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | typedef std::map map_t; 7 | 8 | void *func(void *p) { 9 | map_t& m = *static_cast(p); 10 | m["foo"] = "bar"; 11 | return 0; 12 | } 13 | 14 | int main() { 15 | map_t m; 16 | std::thread t(func, &m); 17 | std::cout << "foo = " << m["foo"] << '\n'; 18 | t.join(); 19 | return 0; 20 | } 21 | -------------------------------------------------------------------------------- /Chapter_12/12x11-TSAN_non_atomic_reference_counting.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class RefCountedObject { 5 | public: 6 | 7 | void Ref() { 8 | ++ref_; 9 | } 10 | 11 | void Unref() { 12 | --ref_; 13 | } 14 | 15 | private: 16 | // ref_ should be atomic to avoid synchronization issues 17 | int ref_{0}; 18 | }; 19 | 20 | int main() { 21 | RefCountedObject obj1; 22 | std::jthread t1(&RefCountedObject::Ref, &obj1); 23 | std::jthread t2(&RefCountedObject::Unref, &obj1); 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /Chapter_12/12x12-TSAN_data_race_pointer_creation.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class MyObj {}; 5 | 6 | static MyObj *obj = nullptr; 7 | 8 | void init_object() { 9 | if (!obj) { 10 | obj = new MyObj(); 11 | } 12 | } 13 | 14 | void func1() { 15 | init_object(); 16 | } 17 | 18 | void func2() { 19 | init_object(); 20 | } 21 | 22 | int main() { 23 | std::thread t1(func1); 24 | std::thread t2(func2); 25 | 26 | t1.join(); 27 | t2.join(); 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /Chapter_12/12x13-UBSAN-uninitialized_memory.cpp: -------------------------------------------------------------------------------- 1 | int main() { 2 | int val = 0x7fffffff; 3 | val += 1; 4 | return 0; 5 | } 6 | -------------------------------------------------------------------------------- /Chapter_12/12x14-MSAN-uninitialized_memory.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | auto arr = new int[10]; 5 | arr[5] = 0; 6 | std::cout << "Value at position 0 = " << arr[0] << '\n'; 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /Chapter_12/12x15-testing_async_function.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std::chrono_literals; 6 | 7 | int asyncFunc() { 8 | std::this_thread::sleep_for(100ms); 9 | return 42; 10 | } 11 | 12 | TEST(AsyncTests, TestHandleAsyncOperation) { 13 | std::future result = std::async(std::launch::async, asyncFunc); 14 | EXPECT_EQ(result.get(), 42); 15 | } 16 | 17 | int main(int argc, char **argv) { 18 | ::testing::InitGoogleTest(&argc, argv); 19 | return RUN_ALL_TESTS(); 20 | } 21 | -------------------------------------------------------------------------------- /Chapter_12/12x16-testing_async_functoin_with_timeout.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std::chrono; 6 | using namespace std::chrono_literals; 7 | 8 | int asyncFunc() { 9 | std::this_thread::sleep_for(100ms); 10 | return 42; 11 | } 12 | 13 | TEST(AsyncTest, TestTimeOut) { 14 | auto start = steady_clock::now(); 15 | std::future result = std::async(std::launch::async, asyncFunc); 16 | 17 | if (result.wait_for(200ms) == std::future_status::timeout) { 18 | FAIL() << "Test timed out!"; 19 | } 20 | 21 | EXPECT_EQ(result.get(), 42); 22 | 23 | auto end = steady_clock::now(); 24 | auto elapsed = duration_cast(end - start); 25 | EXPECT_LT(elapsed.count(), 200); 26 | } 27 | 28 | int main(int argc, char** argv) { 29 | ::testing::InitGoogleTest(&argc, argv); 30 | return RUN_ALL_TESTS(); 31 | } 32 | -------------------------------------------------------------------------------- /Chapter_12/12x17-testing_callbacks.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std::chrono_literals; 9 | 10 | void asyncFunc(std::function callback) { 11 | std::thread([callback]() { 12 | std::this_thread::sleep_for(1s); 13 | callback(42); 14 | }).detach(); 15 | } 16 | 17 | TEST(AsyncTest, TestCallback) { 18 | int result = 0; 19 | bool callback_called = false; 20 | 21 | auto callback = [&](int value) { 22 | callback_called = true; 23 | result = value; 24 | }; 25 | 26 | asyncFunc(callback); 27 | 28 | std::this_thread::sleep_for(2s); 29 | EXPECT_TRUE(callback_called); 30 | EXPECT_EQ(result, 42); 31 | } 32 | 33 | int main(int argc, char** argv) { 34 | ::testing::InitGoogleTest(&argc, argv); 35 | return RUN_ALL_TESTS(); 36 | } 37 | -------------------------------------------------------------------------------- /Chapter_12/12x18-testing_with_dependency_injection.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class AsyncTaskScheduler { 5 | public: 6 | virtual int runTask(std::function task) = 0; 7 | }; 8 | 9 | class MockTaskScheduler : public AsyncTaskScheduler { 10 | public: 11 | int runTask(std::function task) override { 12 | // Directly run the task for testing 13 | return task(); 14 | } 15 | }; 16 | 17 | TEST(AsyncTests, TestDependencyInjection) { 18 | MockTaskScheduler scheduler; 19 | 20 | auto task = []() -> int { 21 | return 42; 22 | }; 23 | 24 | int result = scheduler.runTask(task); 25 | EXPECT_EQ(result, 42); 26 | } 27 | 28 | int main(int argc, char** argv) { 29 | ::testing::InitGoogleTest(&argc, argv); 30 | return RUN_ALL_TESTS(); 31 | } -------------------------------------------------------------------------------- /Chapter_12/12x19-testing_async_function_with_mocking.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | class AsyncTaskScheduler { 6 | public: 7 | virtual int runTask(std::function task) = 0; 8 | }; 9 | 10 | class MockTaskScheduler : public AsyncTaskScheduler { 11 | public: 12 | MOCK_METHOD(int, runTask, (std::function task), (override)); 13 | }; 14 | 15 | TEST(AsyncTests, TestMockMethod) { 16 | using namespace testing; 17 | 18 | MockTaskScheduler scheduler; 19 | 20 | auto task = []() -> int { 21 | return 42; 22 | }; 23 | 24 | EXPECT_CALL(scheduler, runTask(_)).WillOnce( 25 | Invoke(task) 26 | ); 27 | 28 | auto result = scheduler.runTask(task); 29 | EXPECT_EQ(result, 42); 30 | } 31 | 32 | int main(int argc, char** argv) { 33 | ::testing::InitGoogleTest(&argc, argv); 34 | return RUN_ALL_TESTS(); 35 | } 36 | -------------------------------------------------------------------------------- /Chapter_12/12x20-testing_exceptions.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std::chrono_literals; 8 | 9 | int asyncFunc(bool should_fail) { 10 | std::this_thread::sleep_for(100ms); 11 | if (should_fail) { 12 | throw std::runtime_error("Simulated failure"); 13 | } 14 | return 42; 15 | } 16 | 17 | TEST(AsyncTest, TestAsyncFailure1) { 18 | try { 19 | std::future result = std::async(std::launch::async, asyncFunc, true); 20 | result.get(); 21 | FAIL() << "No expected exception thrown"; 22 | } catch (const std::exception& e) { 23 | SUCCEED(); 24 | } 25 | } 26 | 27 | TEST(AsyncTest, TestAsyncFailure2) { 28 | std::future result = std::async(std::launch::async, asyncFunc, true); 29 | EXPECT_ANY_THROW(result.get()); 30 | } 31 | 32 | int main(int argc, char** argv) { 33 | ::testing::InitGoogleTest(&argc, argv); 34 | return RUN_ALL_TESTS(); 35 | } 36 | -------------------------------------------------------------------------------- /Chapter_12/12x21-testing_async_function_with_multiple_threads.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace std::chrono_literals; 12 | 13 | #define sync_cout std::osyncstream(std::cout) 14 | 15 | std::condition_variable cv; 16 | std::mutex mtx; 17 | 18 | bool ready = false; 19 | std::atomic counter = 0; 20 | 21 | const std::size_t num_threads = 5; 22 | 23 | void asyncTask(int id) { 24 | sync_cout << "Thread " << id << ": Starting work..." << std::endl; 25 | std::this_thread::sleep_for(100ms); 26 | sync_cout << "Thread " << id << ": Work finished." << std::endl; 27 | 28 | ++counter; 29 | cv.notify_one(); 30 | } 31 | 32 | TEST(AsyncTest, TestMultipleThreads) { 33 | std::vector threads; 34 | 35 | for (int i = 0; i < num_threads; ++i) { 36 | threads.emplace_back(asyncTask, i + 1); 37 | } 38 | 39 | { 40 | std::unique_lock lock(mtx); 41 | cv.wait_for(lock, 150ms, [] { return counter == num_threads; }); 42 | sync_cout << "All threads have finished." << std::endl; 43 | } 44 | 45 | EXPECT_EQ(counter, num_threads); 46 | } 47 | 48 | int main(int argc, char** argv) { 49 | ::testing::InitGoogleTest(&argc, argv); 50 | return RUN_ALL_TESTS(); 51 | } 52 | -------------------------------------------------------------------------------- /Chapter_12/12x22-testing_async_function_with_event_loop.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std::chrono_literals; 8 | 9 | void asyncFunc(boost::asio::io_context& io_context, std::function callback) { 10 | io_context.post([callback]() { 11 | std::this_thread::sleep_for(100ms); 12 | callback(42); 13 | }); 14 | } 15 | 16 | TEST(AsyncTest, TestBoostAsio) { 17 | boost::asio::io_context io_context; 18 | 19 | int result = 0; 20 | asyncFunc(io_context, [&result](int value) { result = value; }); 21 | 22 | std::jthread io_thread([&io_context]() { io_context.run(); }); 23 | 24 | std::this_thread::sleep_for(150ms); 25 | EXPECT_EQ(result, 42); 26 | } 27 | 28 | int main(int argc, char** argv) { 29 | ::testing::InitGoogleTest(&argc, argv); 30 | return RUN_ALL_TESTS(); 31 | } 32 | -------------------------------------------------------------------------------- /Chapter_12/12x23-testing_coroutines.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | struct Task { 7 | struct promise_type; 8 | using handle_type = std::coroutine_handle; 9 | 10 | struct promise_type { 11 | int result_; 12 | std::exception_ptr exception_; 13 | 14 | Task get_return_object() { 15 | return Task(handle_type::from_promise(*this)); 16 | } 17 | 18 | std::suspend_always initial_suspend() { return {}; } 19 | std::suspend_always final_suspend() noexcept { return {}; } 20 | 21 | void return_value(int value) { 22 | result_ = value; 23 | } 24 | 25 | void unhandled_exception() { 26 | exception_ = std::current_exception(); 27 | } 28 | }; 29 | 30 | handle_type handle_; 31 | 32 | Task(handle_type h) : handle_(h) {} 33 | 34 | ~Task() { 35 | if (handle_) handle_.destroy(); 36 | } 37 | 38 | bool await_ready() const noexcept { 39 | return handle_.done(); 40 | } 41 | 42 | void await_suspend(std::coroutine_handle<> awaiting_handle) { 43 | handle_.resume(); 44 | awaiting_handle.resume(); 45 | } 46 | 47 | int await_resume() { 48 | if (handle_.promise().exception_) { 49 | std::rethrow_exception(handle_.promise().exception_); 50 | } 51 | return handle_.promise().result_; // Access the result from the promise 52 | } 53 | 54 | int result() { 55 | if (handle_.promise().exception_) { 56 | std::rethrow_exception(handle_.promise().exception_); 57 | } 58 | return handle_.promise().result_; 59 | } 60 | }; 61 | 62 | // A coroutine that doubles the input value 63 | Task asyncFunc(int x) { 64 | co_return 2 * x; 65 | } 66 | 67 | // A coroutine that throws an exception 68 | Task asyncFuncWithException() { 69 | throw std::runtime_error("Exception from coroutine"); 70 | co_return 0; 71 | } 72 | 73 | Task TestCoroutineHelper(int value) { 74 | co_return co_await asyncFunc(value); 75 | } 76 | 77 | Task TestCoroutineWithExceptionHelper() { 78 | co_return co_await asyncFuncWithException(); 79 | } 80 | 81 | TEST(AsyncTest, TestCoroutine) { 82 | auto task = TestCoroutineHelper(5); 83 | task.handle_.resume(); 84 | EXPECT_EQ(task.result(), 10); 85 | } 86 | 87 | TEST(AsyncTest, TestCoroutineWithException) { 88 | auto task = TestCoroutineWithExceptionHelper(); 89 | EXPECT_THROW({ 90 | task.handle_.resume(); 91 | task.result(); 92 | }, 93 | std::runtime_error); 94 | } 95 | 96 | int main(int argc, char **argv) { 97 | ::testing::InitGoogleTest(&argc, argv); 98 | return RUN_ALL_TESTS(); 99 | } 100 | -------------------------------------------------------------------------------- /Chapter_12/12x24-stress_testing.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | std::atomic counter(0); 10 | const std::size_t total_runs = 100; 11 | 12 | void asyncIncrement() { 13 | std::this_thread::sleep_for(std::chrono::milliseconds(rand() % 100)); 14 | counter.fetch_add(1); 15 | } 16 | 17 | TEST(AsyncTest, StressTest) { 18 | std::vector threads; 19 | 20 | for (std::size_t i = 0; i < total_runs; ++i) { 21 | threads.emplace_back(asyncIncrement); 22 | } 23 | for (auto& thread : threads) { 24 | thread.join(); 25 | } 26 | EXPECT_EQ(counter, total_runs); 27 | } 28 | 29 | int main(int argc, char** argv) { 30 | ::testing::InitGoogleTest(&argc, argv); 31 | return RUN_ALL_TESTS(); 32 | } 33 | -------------------------------------------------------------------------------- /Chapter_12/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Output directory 2 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BINARY_DIR}/Chapter_12) 3 | 4 | # Option for using Clang complier 5 | # Enabled in this subfolder by default 6 | option(USE_CLANG "Build examples with Clang" ON) 7 | if(USE_CLANG) 8 | message("Using Clang in Chapter 12") 9 | 10 | set(CMAKE_CXX_COMPILER "/usr/bin/clang++") 11 | set(CMAKE_CXX_FLAGS "-Wall") 12 | set(CMAKE_CXX_FLAGS_DEBUG "-g") 13 | set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG") 14 | set(CMAKE_CXX_FLAGS_RELEASE "-O4 -DNDEBUG") 15 | set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g") 16 | 17 | set(CMAKE_AR "/usr/bin/llvm-ar") 18 | set(CMAKE_LINKER "/usr/bin/llvm-ld") 19 | set(CMAKE_NM "/usr/bin/llvm-nm") 20 | set(CMAKE_OBJDUMP "/usr/bin/llvm-objdump") 21 | set(CMAKE_RANLIB "/usr/bin/llvm-ranlib") 22 | else() 23 | message("Using default compiler in Chapter 12") 24 | endif() 25 | 26 | 27 | # Create executable for each cpp file 28 | file(GLOB SOURCES "*.cpp") 29 | foreach(SOURCE ${SOURCES}) 30 | get_filename_component(EXECUTABLE_NAME ${SOURCE} NAME_WE) 31 | add_executable(${EXECUTABLE_NAME} ${SOURCE}) 32 | target_link_libraries(${EXECUTABLE_NAME} ${Boost_LIBRARIES}) 33 | 34 | # Add compiler and linker flags depending on type of sanitizer 35 | if (EXECUTABLE_NAME MATCHES ".*ASAN.*") 36 | message("Using AddressSanitizer for ${EXECUTABLE_NAME}") 37 | target_compile_options(${EXECUTABLE_NAME} PRIVATE -fsanitize=address -fno-omit-frame-pointer -g -O0) 38 | target_link_options(${EXECUTABLE_NAME} PRIVATE -fsanitize=address) 39 | elseif (EXECUTABLE_NAME MATCHES ".*LSAN.*") 40 | message("Using LeakSanitizer for ${EXECUTABLE_NAME}") 41 | target_compile_options(${EXECUTABLE_NAME} PRIVATE -fsanitize=leak -fno-omit-frame-pointer -g -O2) 42 | target_link_options(${EXECUTABLE_NAME} PRIVATE -fsanitize=leak) 43 | elseif (EXECUTABLE_NAME MATCHES ".*MSAN.*") 44 | message("Using MemorySanitizer for ${EXECUTABLE_NAME}") 45 | # -fPIE -pie 46 | target_compile_options(${EXECUTABLE_NAME} PRIVATE -fsanitize=memory -fPIE -fno-omit-frame-pointer -g -O2) 47 | target_link_options(${EXECUTABLE_NAME} PRIVATE -fsanitize=memory) 48 | elseif (EXECUTABLE_NAME MATCHES ".*TSAN.*") 49 | message("Using ThreadSanitizer for ${EXECUTABLE_NAME}") 50 | target_compile_options(${EXECUTABLE_NAME} PRIVATE -fsanitize=thread -fno-omit-frame-pointer -g -O2) 51 | target_link_options(${EXECUTABLE_NAME} PRIVATE -fsanitize=thread) 52 | elseif (EXECUTABLE_NAME MATCHES ".*UBSAN.*") 53 | message("Using UndefinedBehaviorSanitizer for ${EXECUTABLE_NAME}") 54 | target_compile_options(${EXECUTABLE_NAME} PRIVATE -fsanitize=undefined -fno-omit-frame-pointer -g -O2) 55 | target_link_options(${EXECUTABLE_NAME} PRIVATE -fsanitize=undefined) 56 | elseif (EXECUTABLE_NAME MATCHES ".*test.*") 57 | message("Enable testing for ${EXECUTABLE_NAME}") 58 | target_link_libraries(${EXECUTABLE_NAME} GTest::GTest GTest::Main GTest::gmock_main) 59 | gtest_discover_tests(${EXECUTABLE_NAME}) 60 | else() 61 | message("Sanitizer not enabled for ${EXECUTABLE_NAME}") 62 | endif() 63 | 64 | endforeach() 65 | -------------------------------------------------------------------------------- /Chapter_13/13x01-chrono_profile.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int uniform_random_number(int min, int max) { 7 | static std::random_device rd; 8 | static std::mt19937 gen(rd()); 9 | std::uniform_int_distribution<> dis(min, max); 10 | return dis(gen); 11 | } 12 | 13 | std::vector random_vector(std::size_t n, int32_t min_val, int32_t max_val) { 14 | std::vector rv(n); 15 | std::ranges::generate(rv, [&] { return uniform_random_number(min_val, max_val); }); 16 | return rv; 17 | } 18 | 19 | using namespace std::chrono; 20 | 21 | int main() { 22 | constexpr uint32_t elements = 100000000; 23 | int32_t minval = 1; 24 | int32_t maxval = 1000000000; 25 | 26 | auto rv1 = random_vector(elements, minval, maxval); 27 | auto rv2 = rv1; 28 | 29 | auto start = high_resolution_clock::now(); 30 | std::ranges::sort(rv1); 31 | auto end = high_resolution_clock::now(); 32 | auto duration = duration_cast(end - start); 33 | std::cout << "Time to std::sort " 34 | << elements << " elements with values in [" 35 | << minval << "," << maxval << "] " 36 | << duration.count() << " milliseconds\n"; 37 | 38 | start = high_resolution_clock::now(); 39 | std::ranges::stable_sort(rv2); 40 | end = high_resolution_clock::now(); 41 | duration = duration_cast(end - start); 42 | std::cout << "Time to std::stable_sort " 43 | << elements << " elements with values in [" 44 | << minval << "," << maxval << "] " 45 | << duration.count() << " milliseconds\n"; 46 | 47 | return 0; 48 | } -------------------------------------------------------------------------------- /Chapter_13/13x02-benchmark_vector.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | void BM_vector_push_back(benchmark::State& state) { 10 | for (auto _ : state) { 11 | std::vector vec; 12 | for (int i = 0; i < state.range(0); i++) { 13 | vec.push_back(i); 14 | } 15 | } 16 | } 17 | 18 | void BM_vector_emplace_back(benchmark::State& state) { 19 | for (auto _ : state) { 20 | std::vector vec; 21 | for (int i = 0; i < state.range(0); i++) { 22 | vec.emplace_back(i); 23 | } 24 | } 25 | } 26 | 27 | void BM_vector_insert(benchmark::State& state) { 28 | for (auto _ : state) { 29 | std::vector vec; 30 | for (int i = 0; i < state.range(0); i++) { 31 | vec.insert(vec.begin(), i); 32 | } 33 | } 34 | } 35 | 36 | BENCHMARK(BM_vector_push_back)->Range(1, 1000); 37 | BENCHMARK(BM_vector_emplace_back)->Range(1, 1000); 38 | BENCHMARK(BM_vector_insert)->Range(1, 1000); 39 | 40 | int main(int argc, char** argv) { 41 | benchmark::Initialize(&argc, argv); 42 | benchmark::RunSpecifiedBenchmarks(); 43 | 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /Chapter_13/13x03-benchmark_sort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | std::vector rv1, rv2; 10 | 11 | int uniform_random_number(int min, int max) { 12 | static std::random_device rd; 13 | static std::mt19937 gen(rd()); 14 | std::uniform_int_distribution dis(min, max); 15 | return dis(gen); 16 | } 17 | 18 | std::vector random_vector(std::size_t n, int32_t min_val, int32_t max_val) { 19 | std::vector rv(n); 20 | std::ranges::generate(rv, [&] { 21 | return uniform_random_number(min_val, max_val); 22 | }); 23 | return rv; 24 | } 25 | 26 | static void BM_vector_sort(benchmark::State& state, std::vector vec) { 27 | for (auto _ : state) { 28 | std::ranges::sort(vec); 29 | } 30 | } 31 | 32 | static void BM_vector_stable_sort(benchmark::State& state, std::vector vec) { 33 | for (auto _ : state) { 34 | std::ranges::stable_sort(vec); 35 | } 36 | } 37 | 38 | BENCHMARK_CAPTURE(BM_vector_sort, vector, rv1)->Iterations(1)->Unit(benchmark::kMillisecond); 39 | BENCHMARK_CAPTURE(BM_vector_stable_sort, vector, rv2)->Iterations(1)->Unit(benchmark::kMillisecond); 40 | 41 | int main(int argc, char** argv) { 42 | constexpr uint32_t elements = 100000000; 43 | int32_t minval = 1; 44 | int32_t maxval = 1000000000; 45 | 46 | rv1 = random_vector(elements, minval, maxval); 47 | rv2 = rv1; 48 | benchmark::Initialize(&argc, argv); 49 | benchmark::RunSpecifiedBenchmarks(); 50 | 51 | return 0; 52 | } 53 | -------------------------------------------------------------------------------- /Chapter_13/13x04-benchmark_threads.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static void BM_create_terminate_thread(benchmark::State& state) { 10 | for (auto _ : state) { 11 | std::thread thread([]{ return 0; }); 12 | thread.join(); 13 | } 14 | } 15 | 16 | BENCHMARK(BM_create_terminate_thread)->Iterations(2000); 17 | 18 | int main(int argc, char** argv) { 19 | benchmark::Initialize(&argc, argv); 20 | benchmark::RunSpecifiedBenchmarks(); 21 | 22 | return 0; 23 | } 24 | -------------------------------------------------------------------------------- /Chapter_13/13x05-sort_perf.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int uniform_random_number(int min, int max) { 7 | static std::random_device rd; 8 | static std::mt19937 gen(rd()); 9 | std::uniform_int_distribution<> dis(min, max); 10 | return dis(gen); 11 | } 12 | 13 | std::vector random_vector(std::size_t n, int32_t min_val, int32_t max_val) { 14 | std::vector rv(n); 15 | std::generate(rv.begin(), rv.end(), [&] { return uniform_random_number(min_val, max_val); }); 16 | return rv; 17 | } 18 | 19 | int main() { 20 | constexpr uint32_t elements = 100000000; 21 | int32_t minval = 1; 22 | int32_t maxval = 1000000000; 23 | 24 | auto rv = random_vector(elements, minval, maxval); 25 | 26 | auto start = std::chrono::high_resolution_clock::now(); 27 | std::ranges::sort(rv); 28 | auto end = std::chrono::high_resolution_clock::now(); 29 | auto duration = std::chrono::duration_cast(end - start); 30 | 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /Chapter_13/13x06-false_sharing.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | struct result_data { 8 | unsigned long result { 0 }; 9 | }; 10 | 11 | struct alignas(64) aligned_result_data { 12 | unsigned long result { 0 }; 13 | }; 14 | 15 | void set_affinity(int core) { 16 | if (core < 0) { 17 | return; 18 | } 19 | 20 | cpu_set_t cpuset; 21 | CPU_ZERO(&cpuset); 22 | CPU_SET(core, &cpuset); 23 | if (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) != 0) { 24 | perror("pthread_setaffinity_np"); 25 | exit(EXIT_FAILURE); 26 | } 27 | } 28 | 29 | template 30 | auto random_sum(T& data, const std::size_t seed, const unsigned long iterations, const int core) { 31 | set_affinity(core); 32 | std::mt19937 gen(seed); 33 | std::uniform_int_distribution dist(1, 5); 34 | for (unsigned long i = 0; i < iterations; ++i) { 35 | data.result += dist(gen); 36 | } 37 | } 38 | 39 | using namespace std::chrono; 40 | 41 | void sum_random_unaligned(int num_threads, uint32_t iterations) { 42 | auto* data = new(static_cast(64)) result_data[num_threads]; 43 | 44 | auto start = high_resolution_clock::now(); 45 | std::vector threads; 46 | for (std::size_t i = 0; i < num_threads; ++i) { 47 | set_affinity(i); 48 | threads.emplace_back(random_sum, std::ref(data[i]), i, iterations, i); 49 | } 50 | for (auto& thread : threads) { 51 | thread.join(); 52 | } 53 | auto end = high_resolution_clock::now(); 54 | auto duration = std::chrono::duration_cast(end - start); 55 | std::cout << "Non-aligned data: " << duration.count() << " milliseconds" << std::endl; 56 | 57 | operator delete[] (data, static_cast(64)); 58 | } 59 | 60 | void sum_random_aligned(int num_threads, uint32_t iterations) { 61 | auto* aligned_data = new(static_cast(64)) aligned_result_data[num_threads]; 62 | auto start = high_resolution_clock::now(); 63 | std::vector threads; 64 | for (std::size_t i = 0; i < num_threads; ++i) { 65 | set_affinity(i); 66 | threads.emplace_back(random_sum, std::ref(aligned_data[i]), i, iterations, i); 67 | } 68 | for (auto& thread : threads) { 69 | thread.join(); 70 | } 71 | auto end = high_resolution_clock::now(); 72 | auto duration = std::chrono::duration_cast(end - start); 73 | std::cout << "Aligned data: " << duration.count() << " milliseconds" << std::endl; 74 | operator delete[] (aligned_data, static_cast(64)); 75 | } 76 | 77 | int main() { 78 | constexpr unsigned long iterations{ 100000000 }; 79 | constexpr unsigned int num_threads = 8; 80 | 81 | //sum_random_unaligned(8, iterations); 82 | sum_random_aligned(8, iterations); 83 | 84 | return 0; 85 | } -------------------------------------------------------------------------------- /Chapter_13/13x07-thread_contention.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int uniform_random_number(int min, int max) { 9 | static std::random_device rd; 10 | static std::mt19937 gen(rd()); 11 | std::uniform_int_distribution<> dis(min, max); 12 | return dis(gen); 13 | } 14 | 15 | std::vector random_vector(std::size_t n, int32_t min_val, int32_t max_val) { 16 | std::vector rv(n); 17 | std::generate(rv.begin(), rv.end(), [&] { return uniform_random_number(min_val, max_val); }); 18 | return rv; 19 | } 20 | struct thread_data { 21 | std::mutex mtx; 22 | uint32_t idx { 0 }; 23 | std::vector vec; 24 | int64_t sum{ 0ll }; 25 | 26 | thread_data(uint32_t size, int32_t min_val, int32_t max_val) : vec{ random_vector(size, min_val, max_val) } { 27 | std::cout << vec.size() << " " << idx << " " << sum << std::endl; 28 | } 29 | }; 30 | 31 | using namespace std::chrono; 32 | 33 | int main() { 34 | const uint32_t size = 1000000000; 35 | const int min_val = 1; 36 | const int max_val = 10; 37 | thread_data data(size, min_val, max_val); 38 | 39 | auto start = high_resolution_clock::now(); 40 | std::thread th([&] { 41 | for (uint32_t i = 0; i < data.vec.size(); i++) { 42 | data.sum += data.vec[i]; 43 | } 44 | }); 45 | th.join(); 46 | auto end = high_resolution_clock::now(); 47 | auto duration = duration_cast(end - start); 48 | std::cout << "Single thread: " << duration.count() << "usec sum = " << data.sum << std::endl; 49 | 50 | data.idx = 0; 51 | data.sum = 0ll; 52 | int num_threads = 8; 53 | std::vector threads; 54 | start = high_resolution_clock::now(); 55 | for (uint32_t i = 0; i < num_threads; i++) { 56 | threads.emplace_back([&] { 57 | while (true) { 58 | data.mtx.lock(); 59 | if (data.idx == data.vec.size()) { 60 | data.mtx.unlock(); 61 | return; 62 | } 63 | data.sum += data.vec[data.idx++]; 64 | data.mtx.unlock(); 65 | } 66 | }); 67 | } 68 | for (auto& t : threads) { 69 | t.join(); 70 | } 71 | end = high_resolution_clock::now(); 72 | duration = duration_cast(end - start); 73 | std::cout << "Thread contention: " << duration.count() << "usec sum " << data.sum << std::endl; 74 | 75 | return 0; 76 | } -------------------------------------------------------------------------------- /Chapter_13/13x08-spsp_lock_free_queue.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | void set_affinity(int core) { 10 | if (core < 0) { 11 | return; 12 | } 13 | 14 | cpu_set_t cpuset; 15 | CPU_ZERO(&cpuset); 16 | CPU_SET(core, &cpuset); 17 | if (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) != 0) { 18 | perror("pthread_setaffinity_np"); 19 | exit(EXIT_FAILURE); 20 | } 21 | } 22 | 23 | template 24 | class spsc_lock_free_queue { 25 | public: 26 | // capacity must be power of two to avoid using modulo operator when calculating the index 27 | explicit spsc_lock_free_queue(size_t capacity) : capacity_(capacity), buffer_(capacity) { 28 | assert((capacity & (capacity - 1)) == 0 && "capacity must be a power of 2"); 29 | } 30 | 31 | spsc_lock_free_queue(const spsc_lock_free_queue &) = delete; 32 | 33 | spsc_lock_free_queue &operator=(const spsc_lock_free_queue &) = delete; 34 | 35 | bool empty() const { 36 | return tail_.load(std::memory_order_relaxed) == head_.load(std::memory_order_relaxed); 37 | } 38 | 39 | bool push(const T &item) { 40 | std::size_t tail = tail_.load(std::memory_order_relaxed); 41 | std::size_t next_tail = (tail + 1) & (capacity_ - 1); 42 | if (next_tail != head_.load(std::memory_order_acquire)) { 43 | buffer_[tail] = item; 44 | tail_.store(next_tail, std::memory_order_release); 45 | return true; 46 | } 47 | 48 | return false; 49 | } 50 | 51 | bool pop(T &item) { 52 | std::size_t head = head_.load(std::memory_order_relaxed); 53 | if (head == tail_.load(std::memory_order_acquire)) { 54 | return false; 55 | } 56 | 57 | item = buffer_[head]; 58 | head_.store((head + 1) & (capacity_ - 1), std::memory_order_release); 59 | 60 | return true; 61 | } 62 | 63 | private: 64 | alignas(64) std::atomic head_{0}; 65 | alignas(64) std::atomic tail_{0}; 66 | const std::size_t capacity_; 67 | std::vector buffer_; 68 | }; 69 | 70 | template 71 | class spsc_lock_free_queue_optimized { 72 | public: 73 | // capacity must be power of two to avoid using modulo operator when calculating the index 74 | explicit spsc_lock_free_queue_optimized(size_t capacity) : capacity_(capacity), buffer_(capacity) { 75 | assert((capacity & (capacity - 1)) == 0 && "capacity must be a power of 2"); 76 | } 77 | 78 | spsc_lock_free_queue_optimized(const spsc_lock_free_queue_optimized &) = delete; 79 | 80 | spsc_lock_free_queue_optimized &operator=(const spsc_lock_free_queue_optimized &) = delete; 81 | 82 | bool empty() const { 83 | return tail_.load(std::memory_order_relaxed) == head_.load(std::memory_order_relaxed); 84 | } 85 | 86 | bool push(const T &item) { 87 | std::size_t tail = tail_.load(std::memory_order_relaxed); 88 | std::size_t next_tail = (tail + 1) & (capacity_ - 1); 89 | if (next_tail == cache_head_) { 90 | cache_head_ = head_.load(std::memory_order_acquire); 91 | if (next_tail == cache_head_) { 92 | return false; 93 | } 94 | } 95 | 96 | buffer_[tail] = item; 97 | tail_.store(next_tail, std::memory_order_release); 98 | return true; 99 | } 100 | 101 | bool pop(T &item) { 102 | std::size_t head = head_.load(std::memory_order_relaxed); 103 | if (head == cache_tail_) { 104 | cache_tail_ = tail_.load(std::memory_order_acquire); 105 | if (head == cache_tail_) { 106 | return false; 107 | } 108 | } 109 | 110 | item = buffer_[head]; 111 | head_.store((head + 1) & (capacity_ - 1), std::memory_order_release); 112 | return true; 113 | } 114 | 115 | private: 116 | alignas(64) std::atomic head_{0}; 117 | alignas(64) std::atomic tail_{0}; 118 | alignas(64) std::size_t cache_head_{0}; 119 | alignas(64) std::size_t cache_tail_{0}; 120 | const std::size_t capacity_; 121 | std::vector buffer_; 122 | }; 123 | 124 | template 125 | void consumer(T &queue, int core, int iterations) { 126 | set_affinity(core); 127 | for (int i = 0; i < iterations; ++i) { 128 | int data; 129 | while (!queue.pop(data)) { 130 | } 131 | if (data != i) { 132 | throw std::runtime_error(""); 133 | } 134 | } 135 | } 136 | 137 | void test_spsc_lock_free_queue(int64_t iterations, int producer_core, int consumer_core) { 138 | spsc_lock_free_queue queue(1 << 17); 139 | 140 | std::thread th(consumer>, std::ref(queue), consumer_core, iterations); 141 | set_affinity(producer_core); 142 | 143 | const auto start = std::chrono::steady_clock::now(); 144 | for (int i = 0; i < iterations; ++i) { 145 | while (!queue.push(i)) { 146 | } 147 | } 148 | while (!queue.empty()) { 149 | } 150 | th.join(); 151 | const auto end = std::chrono::steady_clock::now(); 152 | 153 | std::cout << iterations * 1000000000 / std::chrono::duration_cast(end - start).count() 154 | << " ops/sec\n"; 155 | } 156 | 157 | void test_optimized_spsc_lock_free_queue(int64_t iterations, int producer_core, int consumer_core) { 158 | spsc_lock_free_queue_optimized queue(1 << 17); 159 | 160 | std::thread th(consumer>, std::ref(queue), consumer_core, iterations); 161 | set_affinity(producer_core); 162 | 163 | const auto start = std::chrono::steady_clock::now(); 164 | for (int i = 0; i < iterations; ++i) { 165 | while (!queue.push(i)) { 166 | } 167 | } 168 | while (!queue.empty()) { 169 | } 170 | th.join(); 171 | const auto end = std::chrono::steady_clock::now(); 172 | 173 | std::cout << iterations * 1000000000 / std::chrono::duration_cast(end - start).count() 174 | << " ops/sec\n"; 175 | } 176 | 177 | int main() { 178 | const int producer_core = 2; 179 | const int consumer_core = 12; 180 | const long long iterations = 1000000000LL; 181 | 182 | test_spsc_lock_free_queue(iterations, producer_core, consumer_core); 183 | test_optimized_spsc_lock_free_queue(iterations, producer_core, consumer_core); 184 | 185 | return 0; 186 | } 187 | -------------------------------------------------------------------------------- /Chapter_13/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Include Benchmark library 2 | find_package(benchmark REQUIRED) 3 | 4 | # Set compiler flags 5 | set(CMAKE_CXX_FLAGS_DEBUG "") 6 | set(CMAKE_CXX_FLAGS_RELEASE "") 7 | set(CMAKE_CXX_FLAGS "-O3 -DNDEBUG -std=c++20 -march=native") 8 | 9 | # Output directory 10 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BINARY_DIR}/Chapter_13) 11 | 12 | # Create executable for each cpp file 13 | file(GLOB SOURCES "*.cpp") 14 | foreach(SOURCE ${SOURCES}) 15 | get_filename_component(EXECUTABLE_NAME ${SOURCE} NAME_WE) 16 | add_executable(${EXECUTABLE_NAME} ${SOURCE}) 17 | target_link_libraries(${EXECUTABLE_NAME} benchmark::benchmark) 18 | endforeach() 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Asynchronous Programming with C++ 2 | 3 | Shipping & Fee Details 4 | 5 | This is the code repository for [Asynchronous Programming with C++](https://www.packtpub.com/en-in/product/asynchronous-programming-with-c-9781835884256), published by Packt. 6 | 7 | **Build blazing-fast software with multithreading and asynchronous programming for ultimate efficiency** 8 | 9 | ## What is this book about? 10 | As hardware advancements continue to accelerate, bringing greater memory capacity and more CPU cores, software must evolve to adapt to efficiently use all available resources and reduce idle CPU cycles. 11 | In this book, two seasoned software engineers with about five decades of combined experience will teach you how to implement concurrent and asynchronous solutions in C++. 12 | 13 | * Explore the different parallel paradigms and know when to apply them 14 | * Acquire deep knowledge of thread management and safety mechanisms 15 | * Understand asynchronous programming in C++, including coroutines 16 | * Leverage network asynchronous programming by using Boost.Asio and Boost.Cobalt 17 | * Add proven performance and optimization techniques to your toolbox 18 | * Find out how to test and debug asynchronous software 19 | 20 | If you feel this book is for you, get your [copy](https://www.amazon.com/Asynchronous-Programming-blazing-fast-multithreading-asynchronous/dp/1835884245/) today! 21 | 22 | https://www.packtpub.com/ 24 | 25 | ## Instructions and Navigations 26 | All of the code is organized into folders. For example, Chapter02. 27 | 28 | The code will look like the following: 29 | ``` 30 | #include 31 | #include 32 | 33 | int main() { 34 | std::thread t1([]() { 35 | for (int i = 0; i < 100; ++i) { 36 | std::cout << "1 " << "2 " << "3 " << "4 " 37 | << std::endl; 38 | } 39 | }); 40 | ``` 41 | **Following is what you need for this book:** 42 | This book is for developers who have some experience using C++, regardless of their professional field. If you want to improve your C++ skills and learn how to develop high-performance software using the latest modern C++ features, this book is for you. 43 | 44 | With the following software and hardware list you can run all code files present in the book (Chapters 3-13). 45 | 46 | ### Software and Hardware List 47 | 48 | | Chapter | Software required | OS required | 49 | | -------- | ------------------------------------| -----------------------------------| 50 | | 3-13| C++20 and C++23| Linux (tested in Ubuntu 24.04)| 51 | | 3-13| GCC 14.2| macOS (tested in macOS Sonoma 14.x)| 52 | | 3-13| Clang 18| Windows 11| 53 | | 3-13| Boost 1.86| | 54 | | 3-13| GDB 15.1| | 55 | 56 | ### Related products 57 | * Mastering Go - Third Edition [[Packt]](https://www.packtpub.com/en-in/product/mastering-go-third-edition-9781801073011) [[Amazon]](https://www.amazon.in/dp/1801079315) 58 | 59 | * Go Programming - From Beginner to Professional - Second Edition [[Packt]](https://www.packtpub.com/en-in/product/go-programming-from-beginner-to-professional-9781803246307) [[Amazon]]() 60 | 61 | ## Get to Know the Authors 62 | **Javier Reguera-Salgado** is a seasoned software engineer with 19+ years of experience, specializing in high-performance computing, real-time data processing, and communication protocols. 63 | Skilled in C++, Python, and a variety of other programming languages and technologies, his work spans low-latency distributed systems, mobile apps, web solutions, and enterprise products. He has contributed to research centers, start-ups, blue-chip companies, and quantitative investment firms in Spain and the UK. 64 | Javier holds a PhD cum laude in high-performance computing from the University of Vigo, Spain. 65 | 66 | **Juan Antonio Rufes** is a software engineer with 30 years of experience, specializing in low-level and systems programming, primarily in C, C++, 0x86 assembly, and Python. 67 | His expertise includes Windows and Linux optimization, Windows kernel drivers for antivirus and encryption, TCP/IP protocol analysis, and low-latency financial systems such as smart order routing and FPGA-based trading systems. He has worked with software companies, investment banks, and hedge funds. 68 | Juan holds an MSc in electrical engineering from the Polytechnic University of Valencia, Spain. 69 | 70 | 71 | -------------------------------------------------------------------------------- /scripts/install_compilers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Install GNU GCC 12, 11, and 10 4 | add-apt-repository ppa:ubuntu-toolchain-r/ppa -y 5 | apt update 6 | apt install -y g++-12 gcc-12 g++-11 gcc-11 g++-10 gcc-10 build-essential 7 | 8 | # Install GNU GCC 13 9 | add-apt-repository ppa:ubuntu-toolchain-r/test -y 10 | apt install -y gcc-13 g++-13 11 | 12 | # Install GNU GCC14 13 | apt install -y gcc-14 g++-14 14 | 15 | # Setup alternatives: Use `update-alternatives --config gcc` to select a specifc compiler. 16 | update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 60 --slave /usr/bin/g++ g++ /usr/bin/g++-14 --slave /usr/bin/gcov gcov /usr/bin/gcov-14 17 | update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 80 --slave /usr/bin/g++ g++ /usr/bin/g++-13 --slave /usr/bin/gcov gcov /usr/bin/gcov-13 18 | update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 100 --slave /usr/bin/g++ g++ /usr/bin/g++-12 --slave /usr/bin/gcov gcov /usr/bin/gcov-12 19 | update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 40 --slave /usr/bin/g++ g++ /usr/bin/g++-11 --slave /usr/bin/gcov gcov /usr/bin/gcov-11 20 | update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 20 --slave /usr/bin/g++ g++ /usr/bin/g++-10 --slave /usr/bin/gcov gcov /usr/bin/gcov-10 21 | 22 | # Install CLang and LLVM tools 23 | apt install -y clang clang-format clang-tidy clang-tools llvm 24 | 25 | # Install CMake 26 | apt install -y cmake 27 | 28 | # Install GoogleTest 29 | apt install -y libgtest-dev libgmock-dev 30 | 31 | # Build and install Google Benchmark 32 | mkdir build_benchmark 33 | cd build_benchmark 34 | git clone https://github.com/google/benchmark.git 35 | cd benchmark 36 | cmake -E make_directory "build" 37 | cmake -DCMAKE_BUILD_TYPE=Release -S . -B "build" 38 | cmake --build "build" --config Release 39 | cmake --build "build" --config Release --target install 40 | cd .. 41 | rm -rf build_benchmark 42 | --------------------------------------------------------------------------------