From f631df4341754fc62f85269da32cd4ff885fc37b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?mq=E7=99=BD?= <3326284481@qq.com> Date: Sun, 8 Sep 2024 10:53:48 +0800 Subject: [PATCH 01/13] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=96=87=E4=BB=B6=EF=BC=8C=E4=BF=AE=E6=94=B9=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E7=AC=94=E8=AF=AF=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + .../01-HelloWorld.cpp | 5 ++ .../CMakeLists.txt | 15 +++++ .../CMakeSettings.json | 63 +++++++++++++++++++ ...37\345\255\220\346\223\215\344\275\234.md" | 2 +- md/README.md | 2 + 6 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 code/ModernCpp-ConcurrentProgramming-Tutorial/01-HelloWorld.cpp create mode 100644 code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt create mode 100644 code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeSettings.json diff --git a/.gitignore b/.gitignore index ab308736..18f381cb 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,8 @@ node_modules/ .vs/ x64/ out/ +*.ilk +*.pdb CMakePresets.json CMakeUserPresets.json diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/01-HelloWorld.cpp b/code/ModernCpp-ConcurrentProgramming-Tutorial/01-HelloWorld.cpp new file mode 100644 index 00000000..b609f207 --- /dev/null +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/01-HelloWorld.cpp @@ -0,0 +1,5 @@ +#include + +int main(){ + std::cout << "Hello World" << std::endl; +} \ No newline at end of file diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt b/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt new file mode 100644 index 00000000..1bd28475 --- /dev/null +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required (VERSION 3.8) + +project ("ModernCpp-ConcurrentProgramming-Tutorial") + +set(CMAKE_CXX_STANDARD 17) + +SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) + +if(MSVC) + add_compile_options("/utf-8" "/permissive-") +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + add_compile_options("-finput-charset=UTF-8" "-fexec-charset=UTF-8") +endif() + +add_executable(ModernCpp-ConcurrentProgramming-Tutorial "01-HelloWorld.cpp") diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeSettings.json b/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeSettings.json new file mode 100644 index 00000000..e254f77b --- /dev/null +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeSettings.json @@ -0,0 +1,63 @@ +{ + "configurations": [ + { + "name": "x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "" + }, + { + "name": "x64-Release", + "generator": "Ninja", + "configurationType": "RelWithDebInfo", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "msvc_x64_x64" ], + "variables": [] + }, + { + "name": "x86-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "msvc_x86" ], + "variables": [] + }, + { + "name": "x86-Release", + "generator": "Ninja", + "configurationType": "RelWithDebInfo", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "msvc_x86" ], + "variables": [] + }, + { + "name": "x64-Clang-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "clang_cl_x64_x64" ], + "variables": [] + } + ] +} \ No newline at end of file diff --git "a/md/05\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216\345\216\237\345\255\220\346\223\215\344\275\234.md" "b/md/05\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216\345\216\237\345\255\220\346\223\215\344\275\234.md" index 7b5c71a1..9909bb6d 100644 --- "a/md/05\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216\345\216\237\345\255\220\346\223\215\344\275\234.md" +++ "b/md/05\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216\345\216\237\345\255\220\346\223\215\344\275\234.md" @@ -791,7 +791,7 @@ RISC-V 采用的也是**弱序内存模型**(weakly-ordered memory model), - `ARM` 和 `RISC-V` 都是弱序内存模型,为什么不同? -各位一定要区分,这种强弱其实也只是一种分类而已,不同的指令集架构大多都还是有所不同的,并不会完全一样。例如: `x86` 的 TSO(Total Store Order)是强一致性模型的一种,但并不是所有强一致性模型都是 TSO +各位一定要区分,这种强弱其实也只是一种分类而已,不同的指令集架构大多都还是有所不同的,并不会完全一样。例如: `x86` 的 TSO(Total Store Order)是强一致性模型的一种,但并不是所有强一致性模型都是 TSO。 ### 宽松定序 diff --git a/md/README.md b/md/README.md index c195ac73..0c942e8f 100644 --- a/md/README.md +++ b/md/README.md @@ -21,6 +21,8 @@   我们的代码风格较为简洁明了,命名全部使用下划线连接,而不是驼峰命名法。花括号通常只占一行,简短的代码可以不额外占行。一般初始化时使用 `{}`,而非 `()` 或者 `=` 。这样简单直观,避免歧义和许多问题。`#include` 引入头文件时需要在尖括号或引号前后加空格。 ```cpp +#include + struct move_only{ move_only() { std::puts("默认构造"); } move_only(move_only&&)noexcept { std::puts("移动构造"); } From 4f434731466edcd618c5f6da07e4cd8a9ed08443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?mq=E7=99=BD?= <3326284481@qq.com> Date: Fri, 13 Sep 2024 20:02:13 +0800 Subject: [PATCH 02/13] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=B8=8D=E9=87=8D?= =?UTF-8?q?=E8=A6=81=E7=9A=84=E6=96=87=E4=BB=B6=E3=80=82=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=20`std::thread`=20=E6=BA=90=E7=A0=81=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E6=8E=AA=E8=BE=9E=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../01-HelloWorld.cpp | 10 +++- .../02-hardware_concurrency.cpp | 59 +++++++++++++++++++ .../03-thread_management.cpp | 32 ++++++++++ .../04-RAII.cpp | 48 +++++++++++++++ ...0\351\200\222\345\217\202\346\225\260.cpp" | 20 +++++++ ...5\345\220\215\347\251\272\351\227\264.cpp" | 28 +++++++++ ...3\346\211\200\346\234\211\346\235\203.cpp" | 18 ++++++ ...0\347\240\201\350\247\243\346\236\220.cpp" | 16 +++++ ...20\347\240\201\350\247\243\346\236\220.md" | 2 +- 9 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 code/ModernCpp-ConcurrentProgramming-Tutorial/02-hardware_concurrency.cpp create mode 100644 code/ModernCpp-ConcurrentProgramming-Tutorial/03-thread_management.cpp create mode 100644 code/ModernCpp-ConcurrentProgramming-Tutorial/04-RAII.cpp create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/05-\344\274\240\351\200\222\345\217\202\346\225\260.cpp" create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/06-this_thread\345\221\275\345\220\215\347\251\272\351\227\264.cpp" create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/07-thread\345\257\271\350\261\241\350\275\254\347\247\273\346\211\200\346\234\211\346\235\203.cpp" create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/08-thread\346\236\204\351\200\240\346\272\220\347\240\201\350\247\243\346\236\220.cpp" diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/01-HelloWorld.cpp b/code/ModernCpp-ConcurrentProgramming-Tutorial/01-HelloWorld.cpp index b609f207..3fbaabd8 100644 --- a/code/ModernCpp-ConcurrentProgramming-Tutorial/01-HelloWorld.cpp +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/01-HelloWorld.cpp @@ -1,5 +1,13 @@ #include +#include +#include -int main(){ +void hello(){ std::cout << "Hello World" << std::endl; + // std::this_thread::sleep_for(std::chrono::seconds(5)); +} + +int main(){ + std::thread t; // 默认构造?构造不关联线程的 thread 对象 + std::cout < +#include +#include +#include +#include +#include + +template +auto sum(ForwardIt first, ForwardIt last) { + using value_type = std::iter_value_t; + std::size_t num_threads = std::thread::hardware_concurrency(); + std::ptrdiff_t distance = std::distance(first, last); + + if (distance > 1024000) { + // 计算每个线程处理的元素数量 + std::size_t chunk_size = distance / num_threads; + std::size_t remainder = distance % num_threads; + + // 存储每个线程的结果 + std::vector results{ num_threads }; + + // 存储关联线程的线程对象 + std::vector threads; + + // 创建并启动线程 + auto start = first; + for (std::size_t i = 0; i < num_threads; ++i) { + auto end = std::next(start, chunk_size + (i < remainder ? 1 : 0)); + threads.emplace_back([start, end, &results, i] { + results[i] = std::accumulate(start, end, value_type{}); + }); + start = end; // 开始迭代器不断向前 + } + + // 等待所有线程执行完毕 + for (auto& thread : threads) + thread.join(); + + // 汇总线程的计算结果 + value_type total_sum = std::accumulate(results.begin(), results.end(), value_type{}); + return total_sum; + } + + value_type total_sum = std::accumulate(first, last, value_type{}); + return total_sum; +} + +int main() { + std::vector vecs{ "1","2","3","4" }; + auto result = sum(vecs.begin(), vecs.end()); + std::cout << result << '\n'; + + vecs.clear(); + for (std::size_t i = 0; i <= 1024001u; ++i) { + vecs.push_back(std::to_string(i)); + } + result = sum(vecs.begin(), vecs.end()); + std::cout << result << '\n'; +} \ No newline at end of file diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/03-thread_management.cpp b/code/ModernCpp-ConcurrentProgramming-Tutorial/03-thread_management.cpp new file mode 100644 index 00000000..0a9f1371 --- /dev/null +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/03-thread_management.cpp @@ -0,0 +1,32 @@ +#include +#include + +struct func { + int& m_i; + func(int& i) :m_i{ i } {} + void operator()(int n)const { + for (int i = 0; i <= n; ++i) { + m_i += i; // 可能悬空引用 + } + } +}; + +void f2() { throw std::runtime_error("test f2()"); } + +void f() { + int n = 0; + std::thread t{ func{n},10 }; + try { + // todo.. 一些当前线程可能抛出异常的代码 + f2(); + t.join(); + } + catch (...) { + t.join(); // 1 + // 如果此处不抛出 会掩盖错误 我们根本没有处理 没有解决 + } +} + +int main() { + f(); +} \ No newline at end of file diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/04-RAII.cpp b/code/ModernCpp-ConcurrentProgramming-Tutorial/04-RAII.cpp new file mode 100644 index 00000000..e3e793af --- /dev/null +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/04-RAII.cpp @@ -0,0 +1,48 @@ +#include +#include +#include + +struct func { + int& m_i; + func(int& i) :m_i{ i } {} + void operator()(int n)const { + for (int i = 0; i <= n; ++i) { + m_i += i; // 可能悬空引用 + } + } +}; + +void f2(){ + // todo.. + throw std::runtime_error("f2 error"); +} + +class thread_guard{ +public: + explicit thread_guard(std::thread& t) :thread_{ t } + {} + ~thread_guard(){ + std::puts("析构"); + if(thread_.joinable()){ // 如果当前有活跃线程 则进行 join + thread_.join(); + } + } + thread_guard& operator=(const thread_guard&) = delete; + thread_guard(const thread_guard&) = delete; + + std::thread& thread_; +}; + +void f() { + int n = 0; + std::thread t{ func{n},10 }; + thread_guard g(t); + f2(); // 可能抛出异常 +} + +int main(){ + // 栈回溯 + try{ + f(); + }catch (...){} +} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/05-\344\274\240\351\200\222\345\217\202\346\225\260.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/05-\344\274\240\351\200\222\345\217\202\346\225\260.cpp" new file mode 100644 index 00000000..1ae06177 --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/05-\344\274\240\351\200\222\345\217\202\346\225\260.cpp" @@ -0,0 +1,20 @@ +#include +#include +#include + +void f(const std::string&); + +void test() { + char buffer[1024]{}; + //todo.. code + std::thread t{ f, std::string(buffer) }; // std::string(buffer) 构造对象,由 std::string 对象自行管理 + t.detach(); +} + +int main(){ + // A 的引用只能引用 A 类型,或者以任何形式 转换到 A + double a = 1; + const int& p = a; // a 隐式转换到了 int 类型,这个转换是纯右值表达式 + // 因为 const T& 可以接右值表达式,所以才能通过编译 + const std::string& s = "123"; // "123" 构造了 std::string 对象 +} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/06-this_thread\345\221\275\345\220\215\347\251\272\351\227\264.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/06-this_thread\345\221\275\345\220\215\347\251\272\351\227\264.cpp" new file mode 100644 index 00000000..b99567e7 --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/06-this_thread\345\221\275\345\220\215\347\251\272\351\227\264.cpp" @@ -0,0 +1,28 @@ +#include +#include +#include +using namespace std::chrono_literals; + +int main() { + // 获取当前时间点 + auto now = std::chrono::system_clock::now(); + + // 设置要等待的时间点为当前时间点之后的5秒 + auto wakeup_time = now + 5s; + + // 输出当前时间 + auto now_time = std::chrono::system_clock::to_time_t(now); + std::cout << "Current time:\t\t" << std::put_time(std::localtime(&now_time), "%H:%M:%S") << std::endl; + + // 输出等待的时间点 + auto wakeup_time_time = std::chrono::system_clock::to_time_t(wakeup_time); + std::cout << "Waiting until:\t\t" << std::put_time(std::localtime(&wakeup_time_time), "%H:%M:%S") << std::endl; + + // 等待到指定的时间点 + std::this_thread::sleep_until(wakeup_time); + + // 输出等待结束后的时间 + now = std::chrono::system_clock::now(); + now_time = std::chrono::system_clock::to_time_t(now); + std::cout << "Time after waiting:\t" << std::put_time(std::localtime(&now_time), "%H:%M:%S") << std::endl; +} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/07-thread\345\257\271\350\261\241\350\275\254\347\247\273\346\211\200\346\234\211\346\235\203.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/07-thread\345\257\271\350\261\241\350\275\254\347\247\273\346\211\200\346\234\211\346\235\203.cpp" new file mode 100644 index 00000000..3d7d267a --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/07-thread\345\257\271\350\261\241\350\275\254\347\247\273\346\211\200\346\234\211\346\235\203.cpp" @@ -0,0 +1,18 @@ +#include +#include + +// https://github.com/Mq-b/Loser-HomeWork/discussions/206 + +// 反直觉 +// 形参、实参 +// 函数调用传参,实际上是初始化了(构造)形参的对象 + +void f(std::thread t) { + t.join(); +} + +int main() { + std::thread t{ [] {} }; + f(std::move(t)); + f(std::thread{ [] {} }); +} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/08-thread\346\236\204\351\200\240\346\272\220\347\240\201\350\247\243\346\236\220.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/08-thread\346\236\204\351\200\240\346\272\220\347\240\201\350\247\243\346\236\220.cpp" new file mode 100644 index 00000000..691290ef --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/08-thread\346\236\204\351\200\240\346\272\220\347\240\201\350\247\243\346\236\220.cpp" @@ -0,0 +1,16 @@ +#include +#include +#include + +struct X { + X(X&& x)noexcept {} + template , X>, int> = 0> + X(Fn&& f, Args&&...args) {} + X(const X&) = delete; +}; + +int main(){ + std::thread + X x{ [] {} }; + X x2{ x }; // 选择到了有参构造函数,不导致编译错误 +} \ No newline at end of file diff --git "a/md/\350\257\246\347\273\206\345\210\206\346\236\220/01thread\347\232\204\346\236\204\351\200\240\344\270\216\346\272\220\347\240\201\350\247\243\346\236\220.md" "b/md/\350\257\246\347\273\206\345\210\206\346\236\220/01thread\347\232\204\346\236\204\351\200\240\344\270\216\346\272\220\347\240\201\350\247\243\346\236\220.md" index 7a56a2fa..cf67dabb 100644 --- "a/md/\350\257\246\347\273\206\345\210\206\346\236\220/01thread\347\232\204\346\236\204\351\200\240\344\270\216\346\272\220\347\240\201\350\247\243\346\236\220.md" +++ "b/md/\350\257\246\347\273\206\345\210\206\346\236\220/01thread\347\232\204\346\236\204\351\200\240\344\270\216\346\272\220\347\240\201\350\247\243\346\236\220.md" @@ -131,7 +131,7 @@ void _Start(_Fn&& _Fx, _Args&&... _Ax) { 4. `constexpr auto _Invoker_proc = _Get_invoke<_Tuple>(make_index_sequence<1 + sizeof...(_Args)>{})` - - 调用 [`_Get_invoke`](https://github.com/microsoft/STL/blob/8e2d724cc1072b4052b14d8c5f81a830b8f1d8cb/stl/inc/thread#L65-L68) 函数,传入 `_Tuple` 类型和一个参数序列的索引序列(为了遍历形参包)。这个函数用于获取一个函数指针,指向了一个静态成员函数 [`_Invoke`](https://github.com/microsoft/STL/blob/8e2d724cc1072b4052b14d8c5f81a830b8f1d8cb/stl/inc/thread#L55-L63),用来实际执行线程。这两个函数都非常的简单,我们来看看: + - 调用 [`_Get_invoke`](https://github.com/microsoft/STL/blob/8e2d724cc1072b4052b14d8c5f81a830b8f1d8cb/stl/inc/thread#L65-L68) 函数,传入 `_Tuple` 类型和一个参数序列的索引序列(为了遍历形参包)。这个函数用于获取一个函数指针,指向了一个静态成员函数 [`_Invoke`](https://github.com/microsoft/STL/blob/8e2d724cc1072b4052b14d8c5f81a830b8f1d8cb/stl/inc/thread#L55-L63),它是线程实际执行的函数。这两个函数都非常的简单,我们来看看: ```cpp template From 79952e402d3bf495e1bbb6984de36b67086d8369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?mq=E7=99=BD?= <3326284481@qq.com> Date: Tue, 17 Sep 2024 20:58:29 +0800 Subject: [PATCH 03/13] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=95=99=E6=A1=88=201.?= =?UTF-8?q?=20=E4=BF=AE=E6=94=B9=E7=AC=AC=E4=BA=8C=E3=80=81=E7=AC=AC?= =?UTF-8?q?=E4=B8=89=E7=AB=A0=E7=9A=84=E9=83=A8=E5=88=86=E5=BE=AE=E5=B0=8F?= =?UTF-8?q?=E6=8E=AA=E8=BE=9E=E4=BB=A5=E5=8F=8A=E4=BB=A3=E7=A0=81=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E3=80=82=202.=20=E6=9B=B4=E6=96=B0=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=B8=AD=E7=9A=84=E4=BB=A3=E7=A0=81=E7=A4=BA?= =?UTF-8?q?=E4=BE=8B=EF=BC=8C=E7=94=A8=E4=BD=9C=E8=A7=86=E9=A2=91=E5=AD=A6?= =?UTF-8?q?=E4=B9=A0=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...345\256\236\347\216\260joining_thread.cpp" | 69 +++++++++++++++++++ .../10-C++20jthread.cpp | 20 ++++++ ...0\346\215\256\347\253\236\344\272\211.cpp" | 19 +++++ ...0\344\272\222\346\226\245\351\207\217.cpp" | 44 ++++++++++++ .../13-try_lock.cpp | 33 +++++++++ ...1\344\272\253\346\225\260\346\215\256.cpp" | 40 +++++++++++ ...0\344\270\216\350\247\243\345\206\263.cpp" | 28 ++++++++ .../16-unique_lock.cpp | 32 +++++++++ ...2\344\272\222\346\226\245\351\207\217.cpp" | 19 +++++ ...3\345\214\226\350\277\207\347\250\213.cpp" | 51 ++++++++++++++ ...0\346\215\256\347\273\223\346\236\204.cpp" | 47 +++++++++++++ .../20recursive_mutex.cpp | 21 ++++++ .../CMakeLists.txt | 6 +- .../test/test_mutex.cpp | 3 + ...77\347\224\250\347\272\277\347\250\213.md" | 4 +- ...61\344\272\253\346\225\260\346\215\256.md" | 13 ++-- 16 files changed, 436 insertions(+), 13 deletions(-) create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/09-\345\256\236\347\216\260joining_thread.cpp" create mode 100644 code/ModernCpp-ConcurrentProgramming-Tutorial/10-C++20jthread.cpp create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/11-\346\225\260\346\215\256\347\253\236\344\272\211.cpp" create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/12-\344\275\277\347\224\250\344\272\222\346\226\245\351\207\217.cpp" create mode 100644 code/ModernCpp-ConcurrentProgramming-Tutorial/13-try_lock.cpp create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/14-\344\277\235\346\212\244\345\205\261\344\272\253\346\225\260\346\215\256.cpp" create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/15-\346\255\273\351\224\201\357\274\232\351\227\256\351\242\230\344\270\216\350\247\243\345\206\263.cpp" create mode 100644 code/ModernCpp-ConcurrentProgramming-Tutorial/16-unique_lock.cpp create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/17-\345\234\250\344\270\215\345\220\214\344\275\234\347\224\250\345\237\237\344\274\240\351\200\222\344\272\222\346\226\245\351\207\217.cpp" create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/18-\344\277\235\346\212\244\345\205\261\344\272\253\346\225\260\346\215\256\347\232\204\345\210\235\345\247\213\345\214\226\350\277\207\347\250\213.cpp" create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/19\344\277\235\346\212\244\344\270\215\345\270\270\346\233\264\346\226\260\347\232\204\346\225\260\346\215\256\347\273\223\346\236\204.cpp" create mode 100644 code/ModernCpp-ConcurrentProgramming-Tutorial/20recursive_mutex.cpp create mode 100644 code/ModernCpp-ConcurrentProgramming-Tutorial/test/test_mutex.cpp diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/09-\345\256\236\347\216\260joining_thread.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/09-\345\256\236\347\216\260joining_thread.cpp" new file mode 100644 index 00000000..ec488654 --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/09-\345\256\236\347\216\260joining_thread.cpp" @@ -0,0 +1,69 @@ +#include +#include +#include +#include +#include + +class joining_thread { + std::thread t; +public: + joining_thread()noexcept = default; + + template + explicit joining_thread(Callable&& func, Args&&...args) : + t{ std::forward(func), std::forward(args)... } {} + + explicit joining_thread(std::thread t_)noexcept : t{ std::move(t_) } {} + + joining_thread(joining_thread&& other)noexcept : t{ std::move(other.t) } {} + + joining_thread& operator=(std::thread&& other)noexcept { + if (joinable()) { // 如果当前有活跃线程(判断当前对象是否持有资源),那就先执行完(先释放) + join(); // 就相当于释放资源一样的意思 + } + t = std::move(other); + return *this; + } + ~joining_thread() { + if (joinable()) { + join(); + } + } + void swap(joining_thread& other)noexcept { + t.swap(other.t); + } + std::thread::id get_id()const noexcept { + return t.get_id(); + } + bool joinable()const noexcept { + return t.joinable(); + } + void join() { + t.join(); + } + void detach() { + t.detach(); + } + std::thread& data()noexcept { + return t; + } + const std::thread& data()const noexcept { + return t; + } +}; + +int main(){ + auto func = []{ + std::cout << std::this_thread::get_id() << '\n'; + }; + + std::vector vec; + + for (int i = 0; i < 10; ++i){ + vec.emplace_back(func); + } + + for(auto& thread : vec){ + thread.join(); + } +} \ No newline at end of file diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/10-C++20jthread.cpp b/code/ModernCpp-ConcurrentProgramming-Tutorial/10-C++20jthread.cpp new file mode 100644 index 00000000..308b281f --- /dev/null +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/10-C++20jthread.cpp @@ -0,0 +1,20 @@ +#include +#include + +using namespace std::literals::chrono_literals; + +void f(std::stop_token stop_token, int value) { + while (!stop_token.stop_requested()) { // 检查是否已经收到停止请求 + std::cout << value++ << ' ' << std::flush; + std::this_thread::sleep_for(200ms); + } + std::cout << std::endl; +} + +int main() { + std::jthread thread{ f, 1 }; // 打印 1..15 大约 3 秒 + std::this_thread::sleep_for(3s); + thread.request_stop(); // 发送信息,线程终止 + std::cout << "乐\n"; + // jthread 的析构函数调用 request_stop() 和 join()。 +} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/11-\346\225\260\346\215\256\347\253\236\344\272\211.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/11-\346\225\260\346\215\256\347\253\236\344\272\211.cpp" new file mode 100644 index 00000000..5607d6c6 --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/11-\346\225\260\346\215\256\347\253\236\344\272\211.cpp" @@ -0,0 +1,19 @@ +#include +#include +#include + +std::vector v; + +int n = 1; + +int main() { + int cnt = 0; + auto f = [&] { cnt++; }; + std::thread t1{ f }, t2{ f }, t3{ f }; // ub 未定义行为 + t1.join(); + t2.join(); + t3.join(); + std::cout << cnt << '\n'; +} +// 数据竞争它是未定义行为,但是 C++ 的编译器,它会假设你的程序(假设程序是对的,代码是对的)没有任何的未定义行为再去进行优化 +// 输出 n,优化,直接缓存这个值 \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/12-\344\275\277\347\224\250\344\272\222\346\226\245\351\207\217.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/12-\344\275\277\347\224\250\344\272\222\346\226\245\351\207\217.cpp" new file mode 100644 index 00000000..534d2410 --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/12-\344\275\277\347\224\250\344\272\222\346\226\245\351\207\217.cpp" @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include +#include +#include + +std::mutex m; + +// 写 +void add_to_list(int n, std::list& list) { + std::vector numbers(n + 1); + std::iota(numbers.begin(), numbers.end(), 0); + int sum = std::accumulate(numbers.begin(), numbers.end(), 0); + + { + std::scoped_lock lc{ m }; + list.push_back(sum); + } +} + +// 读 +void print_list(const std::list& list) { + std::scoped_lock lc{ m }; + for (const auto& i : list) { + std::cout << i << ' '; + } + std::cout << '\n'; +} + +int main(){ + std::list list; + std::thread t1{ add_to_list,10,std::ref(list) }; + std::thread t2{ add_to_list,10,std::ref(list) }; + std::thread t3{ print_list,std::cref(list) }; + std::thread t4{ print_list,std::cref(list) }; + t1.join(); + t2.join(); + t3.join(); + t4.join(); + std::cout << "---------------------\n"; + print_list(list); +} \ No newline at end of file diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/13-try_lock.cpp b/code/ModernCpp-ConcurrentProgramming-Tutorial/13-try_lock.cpp new file mode 100644 index 00000000..fcbbf365 --- /dev/null +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/13-try_lock.cpp @@ -0,0 +1,33 @@ +#include +#include +#include +#include +#include +using namespace std::string_literals; + +std::mutex mtx; + +void thread_function(int id) { + // 尝试加锁 + if (mtx.try_lock()) { + std::string s = "线程:"s + std::to_string(id) + " 获得锁"s + "\n"; + std::string s2 = "线程:"s + std::to_string(id) + " 释放锁"s + "\n"; + std::cout << s; + // 临界区代码 + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟临界区操作 + mtx.unlock(); // 解锁 + std::cout << s2; + } + else { + std::string s = "线程:"s + std::to_string(id) + " 获取锁失败 处理步骤"s + "\n"; + std::cout << s; + } +} + +int main(){ + std::thread t1(thread_function, 1); + std::thread t2(thread_function, 2); + + t1.join(); + t2.join(); +} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/14-\344\277\235\346\212\244\345\205\261\344\272\253\346\225\260\346\215\256.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/14-\344\277\235\346\212\244\345\205\261\344\272\253\346\225\260\346\215\256.cpp" new file mode 100644 index 00000000..d93d694d --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/14-\344\277\235\346\212\244\345\205\261\344\272\253\346\225\260\346\215\256.cpp" @@ -0,0 +1,40 @@ +#include +#include +#include + +class Data { + int a{}; + std::string b{}; +public: + void do_something() { + // 修改数据成员等... + } +}; + +class Data_wrapper { + Data data; + std::mutex m; +public: + template + void process_data(Func func) { + std::lock_guard lc{ m }; + func(data); // 受保护数据传递给函数 + } +}; + +Data* p = nullptr; + +void malicious_function(Data& protected_data) { + p = &protected_data; // 受保护的数据被传递到外部 +} + +Data_wrapper d; + +void foo() { + d.process_data(malicious_function); // 传递了一个恶意的函数 + p->do_something(); // 在无保护的情况下访问保护数据 +} + +int main(){ + +} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/15-\346\255\273\351\224\201\357\274\232\351\227\256\351\242\230\344\270\216\350\247\243\345\206\263.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/15-\346\255\273\351\224\201\357\274\232\351\227\256\351\242\230\344\270\216\350\247\243\345\206\263.cpp" new file mode 100644 index 00000000..dd22b6e2 --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/15-\346\255\273\351\224\201\357\274\232\351\227\256\351\242\230\344\270\216\350\247\243\345\206\263.cpp" @@ -0,0 +1,28 @@ +#include +#include +#include +#include +using namespace std::chrono_literals; + +struct X { + X(const std::string& str) :object{ str } {} + + friend void swap(X& lhs, X& rhs); +private: + std::string object; + std::mutex m; +}; + +void swap(X& lhs, X& rhs) { + if (&lhs == &rhs) return; + std::scoped_lock guard{ lhs.m,rhs.m }; + swap(lhs.object, rhs.object); +} + +int main(){ + X a{ "🤣" }, b{ "😅" }; + std::thread t{ [&] {swap(a, b); } }; // 1 + std::thread t2{ [&] {swap(b, a); } }; // 2 + t.join(); + t2.join(); +} \ No newline at end of file diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/16-unique_lock.cpp b/code/ModernCpp-ConcurrentProgramming-Tutorial/16-unique_lock.cpp new file mode 100644 index 00000000..74e32db7 --- /dev/null +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/16-unique_lock.cpp @@ -0,0 +1,32 @@ +#include +#include +#include +#include +using namespace std::chrono_literals; + +struct X { + X(const std::string& str) :object{ str } {} + + friend void swap(X& lhs, X& rhs); +private: + std::string object; + std::mutex m; +}; + +void swap(X& lhs, X& rhs) { + if (&lhs == &rhs) return; + std::lock(rhs.m, lhs.m); + + std::unique_lock lock1{ lhs.m, std::adopt_lock }; + std::unique_lock lock2{ rhs.m, std::adopt_lock }; + // std::lock(lock1, lock2); + swap(lhs.object, rhs.object); +} + +int main() { + X a{ "🤣" }, b{ "😅" }; + std::thread t{ [&] {swap(a, b); } }; // 1 + std::thread t2{ [&] {swap(b, a); } }; // 2 + t.join(); + t2.join(); +} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/17-\345\234\250\344\270\215\345\220\214\344\275\234\347\224\250\345\237\237\344\274\240\351\200\222\344\272\222\346\226\245\351\207\217.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/17-\345\234\250\344\270\215\345\220\214\344\275\234\347\224\250\345\237\237\344\274\240\351\200\222\344\272\222\346\226\245\351\207\217.cpp" new file mode 100644 index 00000000..dbe08886 --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/17-\345\234\250\344\270\215\345\220\214\344\275\234\347\224\250\345\237\237\344\274\240\351\200\222\344\272\222\346\226\245\351\207\217.cpp" @@ -0,0 +1,19 @@ +#include +#include +#include +#include +#include + +std::unique_lock get_lock() { + extern std::mutex some_mutex; + std::unique_lock lk{ some_mutex }; + return lk; // 选择到 unique_lock 的移动构造,转移所有权 +} +void process_data() { + std::unique_lock lk{ get_lock() }; // 转移到了主函数的 lk 中 + // 执行一些任务... +}// 最后才会 unlock 解锁 + +int main(){ + process_data(); +} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/18-\344\277\235\346\212\244\345\205\261\344\272\253\346\225\260\346\215\256\347\232\204\345\210\235\345\247\213\345\214\226\350\277\207\347\250\213.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/18-\344\277\235\346\212\244\345\205\261\344\272\253\346\225\260\346\215\256\347\232\204\345\210\235\345\247\213\345\214\226\350\277\207\347\250\213.cpp" new file mode 100644 index 00000000..8b19028c --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/18-\344\277\235\346\212\244\345\205\261\344\272\253\346\225\260\346\215\256\347\232\204\345\210\235\345\247\213\345\214\226\350\277\207\347\250\213.cpp" @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include + +struct some{ + void do_something(){} +}; + +std::shared_ptr ptr; +std::once_flag resource_flag; + +void init_resource() { + ptr.reset(new some); +} + +void foo() { + std::call_once(resource_flag, []{ptr.reset(new some); }); // 线程安全的一次初始化 + ptr->do_something(); +} + +void test(){ + std::call_once(resource_flag, [] {std::cout << "f init\n"; }); +} + +std::once_flag flag; +int n = 0; + +void f() { + std::call_once(flag, [] { + ++n; + std::cout << "第 " << n << " 次调用\n"; + throw std::runtime_error("异常"); + }); +} + +class my_class{}; + +inline my_class& get_my_class_instance() { + static my_class instance; // 线程安全的初始化过程 初始化严格发生一次 + return instance; +} + +int main() { + get_my_class_instance(); + get_my_class_instance(); + get_my_class_instance(); + get_my_class_instance(); + get_my_class_instance(); +} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/19\344\277\235\346\212\244\344\270\215\345\270\270\346\233\264\346\226\260\347\232\204\346\225\260\346\215\256\347\273\223\346\236\204.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/19\344\277\235\346\212\244\344\270\215\345\270\270\346\233\264\346\226\260\347\232\204\346\225\260\346\215\256\347\273\223\346\236\204.cpp" new file mode 100644 index 00000000..135048b8 --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/19\344\277\235\346\212\244\344\270\215\345\270\270\346\233\264\346\226\260\347\232\204\346\225\260\346\215\256\347\273\223\346\236\204.cpp" @@ -0,0 +1,47 @@ +#include +#include +#include +#include +#include +#include + +class Settings { +private: + std::map data_; + mutable std::shared_timed_mutex mutex_; // “M&M 规则”:mutable 与 mutex 一起出现 + +public: + void set(const std::string& key, const std::string& value) { + std::lock_guard lock{ mutex_ }; + data_[key] = value; + } + + std::string get(const std::string& key) const { + std::shared_lock lock(mutex_); + auto it = data_.find(key); + return (it != data_.end()) ? it->second : ""; // 如果没有找到键返回空字符串 + } +}; + +Settings set; + +void read(){ + (void)set.get("1"); +} + +void write(){ + set.set("1", "a"); +} + +int main(){ + std::thread t{ read }; + std::thread t2{ read }; + std::thread t3{ read }; + std::thread t4{ read }; + std::thread t5{ write }; + t.join(); + t2.join(); + t3.join(); + t4.join(); + t5.join(); +} \ No newline at end of file diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/20recursive_mutex.cpp b/code/ModernCpp-ConcurrentProgramming-Tutorial/20recursive_mutex.cpp new file mode 100644 index 00000000..aa01fd21 --- /dev/null +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/20recursive_mutex.cpp @@ -0,0 +1,21 @@ +#include +#include +#include + +std::recursive_mutex mtx; + +void recursive_function(int count) { + std::lock_guard lc{ mtx }; + std::cout << "Locked by thread: " << std::this_thread::get_id() << ", count: " << count << std::endl; + if (count > 0) { + recursive_function(count - 1); + } +} + +int main() { + std::thread t1(recursive_function, 3); + std::thread t2(recursive_function, 2); + + t1.join(); + t2.join(); +} \ No newline at end of file diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt b/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt index 1bd28475..94b0963f 100644 --- a/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required (VERSION 3.8) +cmake_minimum_required (VERSION 3.8) project ("ModernCpp-ConcurrentProgramming-Tutorial") @@ -7,9 +7,9 @@ set(CMAKE_CXX_STANDARD 17) SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) if(MSVC) - add_compile_options("/utf-8" "/permissive-") + add_compile_options("/utf-8" "/permissive-" "/Zc:nrvo" "/Od") elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") add_compile_options("-finput-charset=UTF-8" "-fexec-charset=UTF-8") endif() -add_executable(ModernCpp-ConcurrentProgramming-Tutorial "01-HelloWorld.cpp") +add_executable(ModernCpp-ConcurrentProgramming-Tutorial "20recursive_mutex.cpp") diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/test/test_mutex.cpp b/code/ModernCpp-ConcurrentProgramming-Tutorial/test/test_mutex.cpp new file mode 100644 index 00000000..c8a0555d --- /dev/null +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/test/test_mutex.cpp @@ -0,0 +1,3 @@ +#include + +std::mutex some_mutex; diff --git "a/md/02\344\275\277\347\224\250\347\272\277\347\250\213.md" "b/md/02\344\275\277\347\224\250\347\272\277\347\250\213.md" index 07c54015..ef2d5342 100644 --- "a/md/02\344\275\277\347\224\250\347\272\277\347\250\213.md" +++ "b/md/02\344\275\277\347\224\250\347\272\277\347\250\213.md" @@ -974,7 +974,7 @@ _NODISCARD_CTOR_JTHREAD explicit jthread(_Fn&& _Fx, _Args&&... _Ax) { ### 总结 -**零开销原则**应当很好理解。我们本节的难点只在于使用到了一些 MSVC STL 的源码实现来配合理解,其主要在于“线程停止”。线程停止设施你会感觉是一种类似与外部与线程进行某种信号通信的设施,`std::stop_source` 和 `std::stop_token` 都与线程对象关联,然后来管理函数到底如何执行。 +**零开销原则**应当很好理解。我们本节的难点只在于使用到了一些 MSVC STL 的源码实现来配合理解,其主要在于“线程停止”。线程停止设施你会感觉是一种类似于外部与线程进行某种信号通信的设施,`std::stop_source` 和 `std::stop_token` 都与线程对象关联,然后来管理函数到底如何执行。 我们并没有举很多的例子,我们觉得这一个小例子所牵扯到的内容也就足够了,关键在于理解其设计与概念。 @@ -986,6 +986,6 @@ _NODISCARD_CTOR_JTHREAD explicit jthread(_Fn&& _Fx, _Args&&... _Ax) { 本章节的内容围绕着:“使用线程”,也就是"**使用 `std::thread`**"展开, `std::thread` 是我们学习 C++ 并发支持库的重中之重,在最后谈起了 C++20 引入的 `std::jthread` ,它的使用与概念也非常的简单。本章的内容在市面上并不算少见,但是却是少有的**准确与完善**。即使你早已学习乃至使用 C++ 标准库进行多线程编程,我相信本章也一定可以让你收获良多。 -并且如果是第一次学习本章的内容,能会有一些难以理解的地方。建议你多思考、多记忆,并在以后反复查看和实践。 +并且如果是第一次学习本章的内容,可能会有一些难以理解的地方。建议你多思考、多记忆,并在以后反复查看和实践。 我尽量以简单通俗的方式进行讲解。学完本章后,你可能还无法在实际环境利用多线程提升程序效率,至少还需要学习到使用互斥量来保护共享数据,才能实际应用多线程编程。 diff --git "a/md/03\345\205\261\344\272\253\346\225\260\346\215\256.md" "b/md/03\345\205\261\344\272\253\346\225\260\346\215\256.md" index f1633a48..2bd09ee0 100644 --- "a/md/03\345\205\261\344\272\253\346\225\260\346\215\256.md" +++ "b/md/03\345\205\261\344\272\253\346\225\260\346\215\256.md" @@ -75,7 +75,7 @@ std::thread t1{f}, t2{f}, t3{f}; // 未定义行为 ## 使用互斥量 -互斥量(Mutex),又称为互斥锁(或者直接被称作“锁”),是一种用来保护**临界区**[^1]的特殊对象,其相当于实现了一个公共的“**标志位**”。它可以处于锁定(locked)状态,也可以处于解锁(unlocked)状态: +互斥量(Mutex),又常被称为互斥锁、互斥体(或者直接被称作“锁”),是一种用来保护**临界区**[^1]的特殊对象,其相当于实现了一个公共的“**标志位**”。它可以处于锁定(locked)状态,也可以处于解锁(unlocked)状态: 1. 如果互斥量是锁定的,通常说某个特定的线程正持有这个锁。 @@ -336,7 +336,7 @@ public: Data* p = nullptr; void malicious_function(Data& protected_data){ - p = &protected_data; // 受保护的数据被传递 + p = &protected_data; // 受保护的数据被传递到外部 } Data_wrapper d; @@ -482,7 +482,6 @@ void swap(X& lhs, X& rhs) { std::unique_lock lock2{ rhs.m, std::defer_lock }; std::lock(lock1, lock2); swap(lhs.object, rhs.object); - ++n; } ``` @@ -621,11 +620,10 @@ void f() { 一种可能的使用是允许函数去锁住一个互斥量,并将互斥量的所有权转移到调用者上,所以调用者可以在这个锁保护的范围内执行代码。 ```cpp -std::unique_lockget_lock(){ +std::unique_lock get_lock(){ extern std::mutex some_mutex; std::unique_lock lk{ some_mutex }; return lk; - } void process_data(){ std::unique_lock lk{ get_lock() }; @@ -678,8 +676,7 @@ void process_data(){ 2. C++ 标准委员会也认为处理此问题很重要,**所以标准库提供了 [`std::call_once`](https://zh.cppreference.com/w/cpp/thread/call_once) 和 [`std::once_flag`](https://zh.cppreference.com/w/cpp/thread/once_flag)** 来处理这种情况。比起锁住互斥量并显式检查指针,每个线程只需要使用 `std::call_once` 就可以。**使用 `std::call_once` 比显式使用互斥量消耗的资源更少,特别是当初始化完成之后**。 ```cpp - std::shared_ptrptr; - std::mutex m; + std::shared_ptr ptr; std::once_flag resource_flag; void init_resource(){ @@ -811,7 +808,7 @@ int main() { - [**`lock`**](https://zh.cppreference.com/w/cpp/thread/recursive_mutex/lock):线程可以在递归互斥体上重复调用 `lock`。在线程调用 `unlock` 匹配次数后,所有权才会得到**释放**。 -- [**`unlock`**](https://zh.cppreference.com/w/cpp/thread/recursive_mutex/unlock):若所有权层数为 1(此线程对 [lock()](https://zh.cppreference.com/w/cpp/thread/recursive_mutex/lock) 的调用恰好比 `unlock()` 多一次 )则**解锁互斥体**,否则将所有权层数减少 1。 +- [**`unlock`**](https://zh.cppreference.com/w/cpp/thread/recursive_mutex/unlock):若所有权层数为 1(此线程对 [lock()](https://zh.cppreference.com/w/cpp/thread/recursive_mutex/lock) 的调用恰好比 `unlock()` 多一次 )则**解锁互斥量**,否则将所有权层数减少 1。 我们重点的强调了一下这两个成员函数的这个概念,其实也很简单,总而言之就是 `unlock` 必须和 `lock` 的调用次数一样,才会真正解锁互斥量。 From d4af5eda397a63f2bf3cccfb6142f339c940a0bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?mq=E7=99=BD?= <3326284481@qq.com> Date: Fri, 20 Sep 2024 19:53:32 +0800 Subject: [PATCH 04/13] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BB=A5=E5=8F=8ACMake=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=EF=BC=8C=E5=BC=95=E5=85=A5=E4=B8=89=E6=96=B9=E5=BA=93=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E3=80=82=20=E6=95=99=E6=A1=88=E8=BF=98=E6=9C=AA?= =?UTF-8?q?=E5=AE=8C=E6=88=90=EF=BC=8C=E5=85=88=E7=BC=96=E5=86=99=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E7=A4=BA=E4=BE=8B=EF=BC=8C=E5=90=8E=E5=8F=B0=E6=92=AD?= =?UTF-8?q?=E6=94=BE=E9=9F=B3=E4=B9=90=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + ...0\347\232\204\345\220\227\357\274\237.cpp" | 26 +++++ ...3\345\255\230\345\202\250\346\234\237.cpp" | 15 +++ ...4\344\270\216\344\275\277\347\224\250.cpp" | 27 +++++ ...6\346\210\226\346\235\241\344\273\266.cpp" | 32 ++++++ ...0\347\232\204\351\230\237\345\210\227.cpp" | 67 ++++++++++++ .../CMakeLists.txt | 14 ++- .../test.cpp | 25 +++++ .../test/AduioPlayer.h | 103 ++++++++++++++++++ ...14\346\255\245\346\223\215\344\275\234.md" | 6 +- 10 files changed, 312 insertions(+), 4 deletions(-) create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/21new\345\222\214delete\346\230\257\347\272\277\347\250\213\345\256\211\345\205\250\347\232\204\345\220\227\357\274\237.cpp" create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/22\347\272\277\347\250\213\345\255\230\345\202\250\346\234\237.cpp" create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/23\345\261\200\351\203\250\343\200\201\345\205\250\345\261\200\343\200\201\347\272\277\347\250\213\343\200\201CPU\345\217\230\351\207\217\347\232\204\345\257\271\346\257\224\344\270\216\344\275\277\347\224\250.cpp" create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/24\347\255\211\345\276\205\344\272\213\344\273\266\346\210\226\346\235\241\344\273\266.cpp" create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/25\347\272\277\347\250\213\345\256\211\345\205\250\347\232\204\351\230\237\345\210\227.cpp" create mode 100644 code/ModernCpp-ConcurrentProgramming-Tutorial/test.cpp create mode 100644 code/ModernCpp-ConcurrentProgramming-Tutorial/test/AduioPlayer.h diff --git a/.gitignore b/.gitignore index 18f381cb..ac6a9ca8 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ node_modules/ .vs/ x64/ out/ +bin/ *.ilk *.pdb diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/21new\345\222\214delete\346\230\257\347\272\277\347\250\213\345\256\211\345\205\250\347\232\204\345\220\227\357\274\237.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/21new\345\222\214delete\346\230\257\347\272\277\347\250\213\345\256\211\345\205\250\347\232\204\345\220\227\357\274\237.cpp" new file mode 100644 index 00000000..102a0295 --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/21new\345\222\214delete\346\230\257\347\272\277\347\250\213\345\256\211\345\205\250\347\232\204\345\220\227\357\274\237.cpp" @@ -0,0 +1,26 @@ +#include +#include +#include + +struct X { + X(int v) { // 主要防止有人认为构造函数、析构函数啊,是线程安全的 + std::cout << v << " 🤣\n"; + } +}; + +void f() { + X* p = new X{ 1 }; // 存在数据竞争 + delete p; +} + +int main() +{ + for (int i = 0; i < 10; ++i) { + std::thread t{ f }; + std::thread t2{ f }; + t.join(); + t2.join(); + } + + // C++ 保证的是内存的申请和释放 这种全局状态 是线程安全的 +} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/22\347\272\277\347\250\213\345\255\230\345\202\250\346\234\237.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/22\347\272\277\347\250\213\345\255\230\345\202\250\346\234\237.cpp" new file mode 100644 index 00000000..61a6fc4e --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/22\347\272\277\347\250\213\345\255\230\345\202\250\346\234\237.cpp" @@ -0,0 +1,15 @@ +#include +#include + +int global_counter = 0; +__declspec(thread) int thread_local_counter = 0; + +void print_counters() { + std::cout << "global:" << global_counter++ << '\n'; + std::cout << "thread_local:" << thread_local_counter++ << '\n'; +} + +int main() { + std::thread{ print_counters }.join(); + std::thread{ print_counters }.join(); +} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/23\345\261\200\351\203\250\343\200\201\345\205\250\345\261\200\343\200\201\347\272\277\347\250\213\343\200\201CPU\345\217\230\351\207\217\347\232\204\345\257\271\346\257\224\344\270\216\344\275\277\347\224\250.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/23\345\261\200\351\203\250\343\200\201\345\205\250\345\261\200\343\200\201\347\272\277\347\250\213\343\200\201CPU\345\217\230\351\207\217\347\232\204\345\257\271\346\257\224\344\270\216\344\275\277\347\224\250.cpp" new file mode 100644 index 00000000..ce460983 --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/23\345\261\200\351\203\250\343\200\201\345\205\250\345\261\200\343\200\201\347\272\277\347\250\213\343\200\201CPU\345\217\230\351\207\217\347\232\204\345\257\271\346\257\224\344\270\216\344\275\277\347\224\250.cpp" @@ -0,0 +1,27 @@ +#include +#include + +thread_local int n = (std::puts("thread_local init"), 0); + +void f(){ + (void)n; // 防止 gcc 与 clang 优化 + std::puts("f"); +} + +void f2(){ + thread_local static int n = (std::puts("f2 init"), 0); +} + +int main(){ + (void)n; // 防止 gcc 与 clang 优化 + std::cout << "main\n"; + std::thread{ f }.join(); + f2(); + f2(); + f2(); +} + +// gcc 与 clang 存在优化,会出现与 msvc 不同的结果,它们直接将线程变量优化掉了 +// 这应该视作 bug。 +// 视频中想到 std::async 是下一章的内容跳过了(想到的是 msvc 的一个问题),忘记了 gcc 与 clang 此处也存在问题。 +// https://godbolt.org/z/qa6YfMqP7 \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/24\347\255\211\345\276\205\344\272\213\344\273\266\346\210\226\346\235\241\344\273\266.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/24\347\255\211\345\276\205\344\272\213\344\273\266\346\210\226\346\235\241\344\273\266.cpp" new file mode 100644 index 00000000..7c8718bd --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/24\347\255\211\345\276\205\344\272\213\344\273\266\346\210\226\346\235\241\344\273\266.cpp" @@ -0,0 +1,32 @@ +#include +#include +#include +#include +#include +using namespace std::chrono_literals; + +std::mutex mtx; // 互斥量 +std::condition_variable_any cv; // 条件变量 +bool arrived = false; + +void wait_for_arrival() { + std::unique_lock lck(mtx); // 上锁 + cv.wait(lck, [] { return arrived; }); // 等待 arrived 变为 true 会解锁的 再次上锁 + std::cout << "到达目的地,可以下车了!" << std::endl; +} + +void simulate_arrival() { + std::this_thread::sleep_for(std::chrono::seconds(5)); // 模拟地铁到站,假设5秒后到达目的地 + { + std::lock_guard lck(mtx); + arrived = true; // 设置条件变量为 true,表示到达目的地 + } + cv.notify_one(); // 通知等待的线程 +} + +int main(){ + std::thread t{ wait_for_arrival }; + std::thread t2{ simulate_arrival }; + t.join(); + t2.join(); +} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/25\347\272\277\347\250\213\345\256\211\345\205\250\347\232\204\351\230\237\345\210\227.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/25\347\272\277\347\250\213\345\256\211\345\205\250\347\232\204\351\230\237\345\210\227.cpp" new file mode 100644 index 00000000..df90476c --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/25\347\272\277\347\250\213\345\256\211\345\205\250\347\232\204\351\230\237\345\210\227.cpp" @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include +#include +using namespace std::chrono_literals; + +template +class threadsafe_queue { + mutable std::mutex m; // M&M 原则 互斥量,用于保护队列操作的独占访问 + std::condition_variable data_cond; // 条件变量,用于在队列为空时等待 + std::queue data_queue; // 实际存储数据的队列 +public: + threadsafe_queue() {} + + void push(T new_value) { + { + std::lock_guard lk{ m }; + std::cout << "push:" << new_value << std::endl; + data_queue.push(new_value); + } + data_cond.notify_one(); + } + // 从队列中弹出元素(阻塞直到队列不为空) + void pop(T& value) { + std::unique_lock lk{ m }; + data_cond.wait(lk, [this] {return !data_queue.empty(); }); // 解除阻塞 重新获取锁 lock + value = data_queue.front(); + std::cout << "pop:" << value << std::endl; + data_queue.pop(); + } + // 从队列中弹出元素(阻塞直到队列不为空),并返回一个指向弹出元素的 shared_ptr + std::shared_ptr pop() { + std::unique_lock lk{ m }; + data_cond.wait(lk, [this] {return !data_queue.empty(); }); + std::shared_ptrres{ std::make_shared(data_queue.front()) }; + data_queue.pop(); + return res; + } + bool empty()const { + std::lock_guard lk(m); + return data_queue.empty(); + } +}; + +void producer(threadsafe_queue& q) { + for (int i = 0; i < 5; ++i) { + q.push(i); + } +} +void consumer(threadsafe_queue& q) { + for (int i = 0; i < 5; ++i) { + int value{}; + q.pop(value); + } +} + +int main() { + threadsafe_queue q; + + std::thread producer_thread(producer, std::ref(q)); + std::thread consumer_thread(consumer, std::ref(q)); + + producer_thread.join(); + consumer_thread.join(); +} \ No newline at end of file diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt b/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt index 94b0963f..66bd7b5f 100644 --- a/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt @@ -7,9 +7,19 @@ set(CMAKE_CXX_STANDARD 17) SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) if(MSVC) - add_compile_options("/utf-8" "/permissive-" "/Zc:nrvo" "/Od") + add_compile_options("/utf-8" "/permissive-" "/Zc:nrvo") elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") add_compile_options("-finput-charset=UTF-8" "-fexec-charset=UTF-8") endif() -add_executable(ModernCpp-ConcurrentProgramming-Tutorial "20recursive_mutex.cpp") +add_executable(${PROJECT_NAME} "25线程安全的队列.cpp") + + +# 设置 SFML 的 CMake 路径 +set(SFML_DIR "D:/lib/SFML-2.6.1-windows-vc17-64-bit/SFML-2.6.1/lib/cmake/SFML") + +# 查找 SFML 库并设置链接选项 +find_package(SFML 2.6.1 COMPONENTS system window graphics audio network REQUIRED) + +# 链接 SFML 库到项目 +target_link_libraries(${PROJECT_NAME} sfml-system sfml-window sfml-graphics sfml-audio sfml-network) diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/test.cpp b/code/ModernCpp-ConcurrentProgramming-Tutorial/test.cpp new file mode 100644 index 00000000..05d2784d --- /dev/null +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/test.cpp @@ -0,0 +1,25 @@ +#include "test/AduioPlayer.h" + +AudioPlayer audioPlayer; + +int main(){ + audioPlayer.addAudioPath(AudioPlayer::soundResources[4]); + audioPlayer.addAudioPath(AudioPlayer::soundResources[5]); + audioPlayer.addAudioPath(AudioPlayer::soundResources[6]); + audioPlayer.addAudioPath(AudioPlayer::soundResources[7]); + + std::thread t{ []{ + std::this_thread::sleep_for(1s); + audioPlayer.addAudioPath(AudioPlayer::soundResources[1]); + } }; + std::thread t2{ []{ + audioPlayer.addAudioPath(AudioPlayer::soundResources[0]); + } }; + + std::cout << "乐\n"; + + t.join(); + t2.join(); + + std::cout << "end\n"; +} \ No newline at end of file diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/test/AduioPlayer.h b/code/ModernCpp-ConcurrentProgramming-Tutorial/test/AduioPlayer.h new file mode 100644 index 00000000..44142d3a --- /dev/null +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/test/AduioPlayer.h @@ -0,0 +1,103 @@ +#ifndef AUDIOPLAYER_H +#define AUDIOPLAYER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std::chrono_literals; + +class AudioPlayer { +public: + AudioPlayer() : stop{ false }, player_thread{ &AudioPlayer::playMusic, this } + {} + + ~AudioPlayer() { + // 等待队列中所有音乐播放完毕 + while (!audio_queue.empty()) { + std::this_thread::sleep_for(50ms); + } + stop = true; + cond.notify_all(); + if (player_thread.joinable()) { + player_thread.join(); + } + } + + void addAudioPath(const std::string& path) { + std::lock_guard lock{ mtx }; + audio_queue.push(path); + cond.notify_one(); // 通知线程新的音频 + } + +private: + void playMusic() { + while (!stop) { + std::string path; + { + std::unique_lock lock{ mtx }; + cond.wait(lock, [this] { return !audio_queue.empty() || stop; }); + + if (audio_queue.empty()) return; // 防止在对象为空时析构出错 + + path = audio_queue.front(); + audio_queue.pop(); + } + + if (!music.openFromFile(path)) { + std::cerr << "无法加载音频文件: " << path << std::endl; + continue; // 继续播放下一个音频 + } + + music.play(); + + // 等待音频播放完毕 + while (music.getStatus() == sf::SoundSource::Playing) { + sf::sleep(sf::seconds(0.1f)); // sleep 避免忙等占用CPU + } + } + } + + std::atomic stop; + std::thread player_thread; + std::mutex mtx; + std::condition_variable cond; + std::queue audio_queue; + sf::Music music; + +public: + static constexpr std::array soundResources{ + "./sound/01初始化失败.ogg", + "./sound/02初始化成功.ogg", + "./sound/03试剂不足,请添加.ogg", + "./sound/04试剂已失效,请更新.ogg", + "./sound/05清洗液不足,请添加.ogg", + "./sound/06废液桶即将装满,请及时清空.ogg", + "./sound/07废料箱即将装满,请及时清空.ogg", + "./sound/08激发液A液不足,请添加.ogg", + "./sound/09激发液B液不足,请添加.ogg", + "./sound/10反应杯不足,请添加.ogg", + "./sound/11检测全部完成.ogg" + }; + enum SoundIndex { + InitializationFailed, + InitializationSuccessful, + ReagentInsufficient, + ReagentExpired, + CleaningAgentInsufficient, + WasteBinAlmostFull, + WasteContainerAlmostFull, + LiquidAInsufficient, + LiquidBInsufficient, + ReactionCupInsufficient, + DetectionCompleted, + SoundCount // 总音频数量,用于计数 + }; +}; + +#endif // AUDIOPLAYER_H diff --git "a/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" "b/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" index 4060a01e..8b844b52 100644 --- "a/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" +++ "b/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" @@ -1,6 +1,6 @@ # 同步操作 -"同步操作"是指在计算机科学和信息技术中的一种操作方式,其中不同的任务或操作按顺序执行,一个操作完成后才能开始下一个操作。在多线程编程中,各个任务通常需要通过同步操作进行相互**协调和等待**,以确保数据的**一致性**和**正确性**。 +"同步操作"是指在计算机科学和信息技术中的一种操作方式,其中不同的任务或操作按顺序执行,一个操作完成后才能开始下一个操作。在多线程编程中,各个任务通常需要通过**同步设施**进行相互**协调和等待**,以确保数据的**一致性**和**正确性**。 本章的主要内容有: @@ -146,7 +146,7 @@ void wait(unique_lock& _Lck, _Predicate _Pred) { ## 线程安全的队列 -在本节中,我们介绍了一个更为复杂的示例,以巩固我们对条件变量的学习。为了实现一个线程安全的队列,我们需要考虑以下两个关键点: +在本节中,我们介将绍一个更为复杂的示例,以巩固我们对条件变量的学习。为了实现一个线程安全的队列,我们需要考虑以下两个关键点: 1. 当执行 `push` 操作时,需要确保没有其他线程正在执行 `push` 或 `pop` 操作;同样,在执行 `pop` 操作时,也需要确保没有其他线程正在执行 `push` 或 `pop` 操作。 @@ -286,6 +286,8 @@ Consumer 线程弹出元素 4: 到此,也就可以了。 +## 使用条件变量实现后台音乐播放 + ## 使用 `future` 举个例子:我们在车站等车,你可能会做一些别的事情打发时间,比如学习[现代 C++ 模板教程](https://github.com/Mq-b/Modern-Cpp-templates-tutorial)、观看 [mq白](https://space.bilibili.com/1292761396) 的视频教程、玩手机等。不过,你始终在等待一件事情:***车到站***。 From 8954e3c9f1b9fb153f2ac016ba85bcb48e47012b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?mq=E7=99=BD?= <3326284481@qq.com> Date: Sat, 21 Sep 2024 11:36:06 +0800 Subject: [PATCH 05/13] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=B8=80=E8=8A=82?= =?UTF-8?q?=EF=BC=9A=E4=BD=BF=E7=94=A8=E6=9D=A1=E4=BB=B6=E5=8F=98=E9=87=8F?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=90=8E=E5=8F=B0=E6=8F=90=E7=A4=BA=E9=9F=B3?= =?UTF-8?q?=E6=92=AD=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...2\351\237\263\346\222\255\346\224\276.cpp" | 106 +++++++++++++++ .../CMakeLists.txt | 6 +- .../test/AduioPlayer.h | 26 ++-- ...14\346\255\245\346\223\215\344\275\234.md" | 127 +++++++++++++++++- 4 files changed, 247 insertions(+), 18 deletions(-) create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/26\344\275\277\347\224\250\346\235\241\344\273\266\345\217\230\351\207\217\345\256\236\347\216\260\345\220\216\345\217\260\346\217\220\347\244\272\351\237\263\346\222\255\346\224\276.cpp" diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/26\344\275\277\347\224\250\346\235\241\344\273\266\345\217\230\351\207\217\345\256\236\347\216\260\345\220\216\345\217\260\346\217\220\347\244\272\351\237\263\346\222\255\346\224\276.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/26\344\275\277\347\224\250\346\235\241\344\273\266\345\217\230\351\207\217\345\256\236\347\216\260\345\220\216\345\217\260\346\217\220\347\244\272\351\237\263\346\222\255\346\224\276.cpp" new file mode 100644 index 00000000..88e4ca18 --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/26\344\275\277\347\224\250\346\235\241\344\273\266\345\217\230\351\207\217\345\256\236\347\216\260\345\220\216\345\217\260\346\217\220\347\244\272\351\237\263\346\222\255\346\224\276.cpp" @@ -0,0 +1,106 @@ +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std::chrono_literals; + +class AudioPlayer{ +public: + AudioPlayer() : stop {false}, player_thread{ &AudioPlayer::playMusic, this } + {} + + ~AudioPlayer(){ + while (!audio_queue.empty()){ + std::this_thread::sleep_for(50ms); + } + stop = true; + cond.notify_all(); + if(player_thread.joinable()){ + player_thread.join(); + } + } + + void addAudioPath(const std::string& path){ + std::lock_guard lc{ m }; + audio_queue.push(path); + cond.notify_one(); + } + +private: + void playMusic(){ + while(!stop){ + std::string path; + { + std::unique_lock lock{ m }; + // 条件不满足,就解锁 unlock,让其它线程得以运行 如果被唤醒了,就会重新获取锁 lock + cond.wait(lock, [this] {return !audio_queue.empty() || stop; }); + + if (audio_queue.empty()) return; // 防止对象为空时出问题 + + path = audio_queue.front(); // 取出 + audio_queue.pop(); // 取出后就删除这个元素,表示此元素以及被使用 + } + + if(!music.openFromFile(path)){ + std::cerr << "无法加载音频文件: " << path << std::endl; + continue; + } + + music.play(); // 异步 非阻塞 + + while(music.getStatus() == sf::SoundSource::Playing){ + sf::sleep(sf::seconds(0.1f)); // sleep 避免忙等 占用 CPU + } + } + } + + std::atomic stop; // 控制线程的停止与退出 + std::thread player_thread; // 后台执行音频播放任务的专用线程 + std::mutex m; // 保护共享资源 + std::condition_variable cond; // 控制线程的等待和唤醒,当有新的任务的时候通知播放线程 + std::queue audio_queue; // 音频任务队列,存储待播放的音频文件的路径 + sf::Music music; // SFML 音频播放器对象,用来加载播放音频 + +public: + static constexpr std::array soundResources{ + "./sound/01初始化失败.ogg", + "./sound/02初始化成功.ogg", + "./sound/03试剂不足,请添加.ogg", + "./sound/04试剂已失效,请更新.ogg", + "./sound/05清洗液不足,请添加.ogg", + "./sound/06废液桶即将装满,请及时清空.ogg", + "./sound/07废料箱即将装满,请及时清空.ogg", + "./sound/08激发液A液不足,请添加.ogg", + "./sound/09激发液B液不足,请添加.ogg", + "./sound/10反应杯不足,请添加.ogg", + "./sound/11检测全部完成.ogg" + }; +}; + +AudioPlayer audioPlayer; + +int main() { + audioPlayer.addAudioPath(AudioPlayer::soundResources[4]); + audioPlayer.addAudioPath(AudioPlayer::soundResources[5]); + audioPlayer.addAudioPath(AudioPlayer::soundResources[6]); + audioPlayer.addAudioPath(AudioPlayer::soundResources[7]); + + std::thread t{ [] { + std::this_thread::sleep_for(1s); + audioPlayer.addAudioPath(AudioPlayer::soundResources[1]); + } }; + std::thread t2{ [] { + audioPlayer.addAudioPath(AudioPlayer::soundResources[0]); + } }; + + std::cout << "乐\n"; + + t.join(); + t2.join(); + + std::cout << "end\n"; +} \ No newline at end of file diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt b/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt index 66bd7b5f..d90bdce2 100644 --- a/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt @@ -12,14 +12,14 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "C add_compile_options("-finput-charset=UTF-8" "-fexec-charset=UTF-8") endif() -add_executable(${PROJECT_NAME} "25线程安全的队列.cpp") +add_executable(${PROJECT_NAME} "26使用条件变量实现后台提示音播放.cpp") # 设置 SFML 的 CMake 路径 set(SFML_DIR "D:/lib/SFML-2.6.1-windows-vc17-64-bit/SFML-2.6.1/lib/cmake/SFML") -# 查找 SFML 库并设置链接选项 +# 查找 SFML 库 find_package(SFML 2.6.1 COMPONENTS system window graphics audio network REQUIRED) -# 链接 SFML 库到项目 +# 链接 SFML 库到项目 设置链接选项 target_link_libraries(${PROJECT_NAME} sfml-system sfml-window sfml-graphics sfml-audio sfml-network) diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/test/AduioPlayer.h b/code/ModernCpp-ConcurrentProgramming-Tutorial/test/AduioPlayer.h index 44142d3a..2b51ecd5 100644 --- a/code/ModernCpp-ConcurrentProgramming-Tutorial/test/AduioPlayer.h +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/test/AduioPlayer.h @@ -30,9 +30,9 @@ class AudioPlayer { } void addAudioPath(const std::string& path) { - std::lock_guard lock{ mtx }; - audio_queue.push(path); - cond.notify_one(); // 通知线程新的音频 + std::lock_guard lock{ mtx }; // 互斥量确保了同一时间不会有其它地方在操作共享资源(队列) + audio_queue.push(path); // 为队列添加元素 表示有新的提示音需要播放 + cond.notify_one(); // 通知线程新的音频 } private: @@ -45,8 +45,8 @@ class AudioPlayer { if (audio_queue.empty()) return; // 防止在对象为空时析构出错 - path = audio_queue.front(); - audio_queue.pop(); + path = audio_queue.front(); // 从队列中取出元素 + audio_queue.pop(); // 取出后就删除元素,表示此元素已被使用 } if (!music.openFromFile(path)) { @@ -58,18 +58,18 @@ class AudioPlayer { // 等待音频播放完毕 while (music.getStatus() == sf::SoundSource::Playing) { - sf::sleep(sf::seconds(0.1f)); // sleep 避免忙等占用CPU + sf::sleep(sf::seconds(0.1f)); // sleep 避免忙等占用 CPU } } } - std::atomic stop; - std::thread player_thread; - std::mutex mtx; - std::condition_variable cond; - std::queue audio_queue; - sf::Music music; - + std::atomic stop; // 控制线程的停止与退出, + std::thread player_thread; // 后台执行音频任务的专用线程 + std::mutex mtx; // 保护共享资源 + std::condition_variable cond; // 控制线程等待和唤醒,当有新任务时通知音频线程 + std::queue audio_queue; // 音频任务队列,存储待播放的音频文件路径 + sf::Music music; // SFML 音频播放器,用于加载和播放音频文件 + public: static constexpr std::array soundResources{ "./sound/01初始化失败.ogg", diff --git "a/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" "b/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" index 8b844b52..b21bbb15 100644 --- "a/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" +++ "b/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" @@ -180,7 +180,7 @@ public: std::shared_ptr pop() { std::unique_lock lk{ m }; data_cond.wait(lk, [this] {return !data_queue.empty(); }); - std::shared_ptrres { std::make_shared(data_queue.front()) }; + std::shared_ptr res { std::make_shared(data_queue.front()) }; data_queue.pop(); return res; } @@ -286,7 +286,130 @@ Consumer 线程弹出元素 4: 到此,也就可以了。 -## 使用条件变量实现后台音乐播放 +## 使用条件变量实现后台提示音播放 + +一个常见的场景是:当你的软件完成了主要功能后,领导可能突然要求添加一些竞争对手产品的功能。比如领导看到了人家的设备跑起来总是有一些播报,说明当前的情况,执行的过程,或者报错了也会有提示音说明。于是就想让我们的程序也增加“**语音提示**”的功能。此时,你需要考虑如何在程序运行到不同状态时添加适当的语音播报,并且**确保这些提示音的播放不会影响其他功能的正常运行**。 + +为了不影响程序的流畅执行,提示音的播放显然不能占据业务线程的资源。我们需要额外启动一个线程来专门处理这个任务。 + +但是,大多数的提示音播放都是短暂且简单。如果每次播放提示音时都新建一个线程,且不说创建线程也需要大量时间,可能影响业务正常的执行任务的流程,就光是其频繁创建线程的开销也是不能接受的。 + +--- + +因此,更合理的方案是:**在程序启动时,就启动一个专门用于播放提示音的线程。当没有需要播放的提示时,该线程会一直处于等待状态;一旦有提示音需要播放,线程就被唤醒,完成播放任务**。 + +具体来说,我们可以通过条件变量来实现这一逻辑,核心是监控一个音频队列。我们可以封装一个类型,包含以下功能: + +- 一个成员函数在对象构造时就启动,使用条件变量监控队列是否为空,互斥量确保共享资源的同步。如果队列中有任务,就取出并播放提示音;如果队列为空,则线程保持阻塞状态,等待新的任务到来。 +- 提供一个外部函数,以供在需要播放提示音的时候调用它,向队列添加新的元素,该函数需要通过互斥量来保护数据一致性,并在成功添加任务后唤醒条件变量,通知播放线程执行任务。 + +> 这种设计通过合理利用**条件变量**和**互斥量**,不仅有效减少了 CPU 的无效开销,还能够确保主线程的顺畅运行。它不仅适用于提示音的播放,还能扩展用于其他类似的后台任务场景。 + +我们引入 [SFML](https://github.com/SFML/SFML) 三方库进行声音播放,然后再自己进行上层封装。 + +```CPP +class AudioPlayer { +public: + AudioPlayer() : stop{ false }, player_thread{ &AudioPlayer::playMusic, this } + {} + + ~AudioPlayer() { + // 等待队列中所有音乐播放完毕 + while (!audio_queue.empty()) { + std::this_thread::sleep_for(50ms); + } + stop = true; + cond.notify_all(); + if (player_thread.joinable()) { + player_thread.join(); + } + } + + void addAudioPath(const std::string& path) { + std::lock_guard lock{ mtx }; // 互斥量确保了同一时间不会有其它地方在操作共享资源(队列) + audio_queue.push(path); // 为队列添加元素 表示有新的提示音需要播放 + cond.notify_one(); // 通知线程新的音频 + } + +private: + void playMusic() { + while (!stop) { + std::string path; + { + std::unique_lock lock{ mtx }; + cond.wait(lock, [this] { return !audio_queue.empty() || stop; }); + + if (audio_queue.empty()) return; // 防止在对象为空时析构出错 + + path = audio_queue.front(); // 从队列中取出元素 + audio_queue.pop(); // 取出后就删除元素,表示此元素已被使用 + } + + if (!music.openFromFile(path)) { + std::cerr << "无法加载音频文件: " << path << std::endl; + continue; // 继续播放下一个音频 + } + + music.play(); + + // 等待音频播放完毕 + while (music.getStatus() == sf::SoundSource::Playing) { + sf::sleep(sf::seconds(0.1f)); // sleep 避免忙等占用 CPU + } + } + } + + std::atomic stop; // 控制线程的停止与退出, + std::thread player_thread; // 后台执行音频任务的专用线程 + std::mutex mtx; // 保护共享资源 + std::condition_variable cond; // 控制线程等待和唤醒,当有新任务时通知音频线程 + std::queue audio_queue; // 音频任务队列,存储待播放的音频文件路径 + sf::Music music; // SFML 音频播放器,用于加载和播放音频文件 +}; +``` + +该代码实现了一个简单的**后台音频播放类型**,通过**条件变量**和**互斥量**确保播放线程 `playMusic` 只在只在**有音频任务需要播放时工作**(当外部通过调用 `addAudioPath()` 向队列添加播放任务时)。在没有任务时,线程保持等待状态,避免占用 CPU 资源影响主程序的运行。 + +此外,关于提示音的播报,为了避免每次都手动添加路径,我们可以创建一个音频资源数组,便于使用: + +```cpp +static constexpr std::array soundResources{ + "./sound/01初始化失败.ogg", + "./sound/02初始化成功.ogg", + "./sound/03试剂不足,请添加.ogg", + "./sound/04试剂已失效,请更新.ogg", + "./sound/05清洗液不足,请添加.ogg", + "./sound/06废液桶即将装满,请及时清空.ogg", + "./sound/07废料箱即将装满,请及时清空.ogg", + "./sound/08激发液A液不足,请添加.ogg", + "./sound/09激发液B液不足,请添加.ogg", + "./sound/10反应杯不足,请添加.ogg", + "./sound/11检测全部完成.ogg" +}; +``` + +为了提高代码的可读性,我们还可以使用一个枚举类型来表示音频资源的索引: + +```cpp +enum SoundIndex { + InitializationFailed, + InitializationSuccessful, + ReagentInsufficient, + ReagentExpired, + CleaningAgentInsufficient, + WasteBinAlmostFull, + WasteContainerAlmostFull, + LiquidAInsufficient, + LiquidBInsufficient, + ReactionCupInsufficient, + DetectionCompleted, + SoundCount // 总音频数量,用于计数 +}; +``` + +需要注意的是 SFML不支持 `.mp3` 格式的音频文件,大家可以使用 ffmpeg 或者其它软件[网站](https://www.freeconvert.com/audio-converter)将音频转换为支持的格式。 + +如果是测试使用,不知道去哪生成这些语音播报,我们推荐 [`tts-vue`](https://github.com/LokerL/tts-vue)。 ## 使用 `future` From de1f91afe1f778006c4b5505382d8b6e6d3f483a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?mq=E7=99=BD?= <3326284481@qq.com> Date: Sun, 22 Sep 2024 19:11:42 +0800 Subject: [PATCH 06/13] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=EF=BC=8C=E4=BF=AE=E6=94=B9=E7=AC=AC=E5=9B=9B?= =?UTF-8?q?=E7=AB=A0=E6=8E=AA=E8=BE=9E=E3=80=81=E6=A0=BC=E5=BC=8F=E3=80=81?= =?UTF-8?q?=E4=BB=A5=E5=8F=8A=E8=A1=A5=E5=85=85=E4=BB=A3=E7=A0=81=E7=A4=BA?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...6\350\277\224\345\233\236\345\200\274.cpp" | 13 +++++ .../28future\344\270\216 packaged_task.cpp" | 46 ++++++++++++++++ .../29\344\275\277\347\224\250promise.cpp" | 55 +++++++++++++++++++ ...6\346\200\201\345\217\230\345\214\226.cpp" | 16 ++++++ ...\347\255\211\345\276\205shared_future.cpp" | 37 +++++++++++++ ...\345\276\205-\346\227\266\351\222\237.cpp" | 14 +++++ .../CMakeLists.txt | 2 +- ...14\346\255\245\346\223\215\344\275\234.md" | 50 +++++++++++++---- 8 files changed, 222 insertions(+), 11 deletions(-) create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/27\345\210\233\345\273\272\345\274\202\346\255\245\344\273\273\345\212\241\350\216\267\345\217\226\350\277\224\345\233\236\345\200\274.cpp" create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/28future\344\270\216 packaged_task.cpp" create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/29\344\275\277\347\224\250promise.cpp" create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/30future\347\232\204\347\212\266\346\200\201\345\217\230\345\214\226.cpp" create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/31\345\244\232\344\270\252\347\272\277\347\250\213\347\232\204\347\255\211\345\276\205shared_future.cpp" create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/32\351\231\220\346\227\266\347\255\211\345\276\205-\346\227\266\351\222\237.cpp" diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/27\345\210\233\345\273\272\345\274\202\346\255\245\344\273\273\345\212\241\350\216\267\345\217\226\350\277\224\345\233\236\345\200\274.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/27\345\210\233\345\273\272\345\274\202\346\255\245\344\273\273\345\212\241\350\216\267\345\217\226\350\277\224\345\233\236\345\200\274.cpp" new file mode 100644 index 00000000..cb33de38 --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/27\345\210\233\345\273\272\345\274\202\346\255\245\344\273\273\345\212\241\350\216\267\345\217\226\350\277\224\345\233\236\345\200\274.cpp" @@ -0,0 +1,13 @@ +#include +#include +#include // 引入 future 头文件 + +void f() { + std::cout << std::this_thread::get_id() << '\n'; +} + +int main() { + auto t = std::async([] {}); + std::future future{ std::move(t) }; + future.wait(); // Error! 抛出异常 +} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/28future\344\270\216 packaged_task.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/28future\344\270\216 packaged_task.cpp" new file mode 100644 index 00000000..abfa730a --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/28future\344\270\216 packaged_task.cpp" @@ -0,0 +1,46 @@ +#include +#include +#include + +template +void async_task(std::packaged_task& task, Args&&...args) { + // todo.. + task(std::forward(args)...); +} + +int main() { + std::packaged_task task([](int a, int b) { + return a + b; + }); + + int value = 50; + + std::future future = task.get_future(); + + // 创建一个线程来执行异步任务 + std::thread t{ [&] { async_task(task, value, value); } }; + std::cout << future.get() << '\n'; + t.join(); +} + +//int main(){ +// std::cout << "main: " << std::this_thread::get_id() << '\n'; +// +// // 只能移动不能复制 +// std::packaged_task task{ [](int a, int b) { +// std::cout << "packaged_task: " << std::this_thread::get_id() << '\n'; +// return std::pow(a, b); +// } }; +// +// std::future future = task.get_future(); +// +// // task(10, 2); // 调用 此处执行任务 +// +// std::thread t{ std::move(task) ,10,2 }; +// +// std::cout << "------\n"; +// +// std::cout << future.get() << '\n'; // 会阻塞,直到任务执行完毕 +// +// t.join(); +//} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/29\344\275\277\347\224\250promise.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/29\344\275\277\347\224\250promise.cpp" new file mode 100644 index 00000000..c37e3070 --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/29\344\275\277\347\224\250promise.cpp" @@ -0,0 +1,55 @@ +#include +#include +#include +#include +using namespace std::chrono_literals; + +void f(std::promise obj ,int num){ + // todo.. + obj.set_value(num * num); // 调用了 set_value + // todo.. + std::this_thread::sleep_for(5s); // 模拟一些计算 +} + +void throw_function(std::promise prom) { + prom.set_value(100); + try { + // todo.. + throw std::runtime_error("一个异常"); + } + catch (...) { + try { + // 共享状态的 promise 已存储值,调用 set_exception 产生异常 + prom.set_exception(std::current_exception()); + } + catch (std::exception& e) { + std::cerr << "来自 set_exception 的异常: " << e.what() << '\n'; + } + } +} + +int main() { + std::promise prom; + std::future fut = prom.get_future(); + + std::thread t(throw_function, std::move(prom)); + + std::cout << "等待线程执行,抛出异常并设置\n"; + std::cout << "值:" << fut.get() << '\n'; // 100 + + t.join(); +} + + +//int main(){ +// std::promise promise; +// +// auto future = promise.get_future(); // 关联了 +// +// std::thread t{ f,std::move(promise), 10 }; +// // f(std::move(promise), 10); +// +// std::cout << future.get() << '\n'; // 阻塞,直至结果可用 +// std::cout << "end\n"; +// t.join(); +//} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/30future\347\232\204\347\212\266\346\200\201\345\217\230\345\214\226.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/30future\347\232\204\347\212\266\346\200\201\345\217\230\345\214\226.cpp" new file mode 100644 index 00000000..b6cb59b6 --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/30future\347\232\204\347\212\266\346\200\201\345\217\230\345\214\226.cpp" @@ -0,0 +1,16 @@ +#include +#include +#include + +int main(){ + std::futurefuture = std::async([] {}); + std::cout << std::boolalpha << future.valid() << '\n'; // true + future.get(); + std::cout << std::boolalpha << future.valid() << '\n'; // false + try { + future.get(); // 抛出 future_errc::no_state 异常 + } + catch (std::exception& e) { + std::cerr << e.what() << '\n'; + } +} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/31\345\244\232\344\270\252\347\272\277\347\250\213\347\232\204\347\255\211\345\276\205shared_future.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/31\345\244\232\344\270\252\347\272\277\347\250\213\347\232\204\347\255\211\345\276\205shared_future.cpp" new file mode 100644 index 00000000..7ada4b4a --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/31\345\244\232\344\270\252\347\272\277\347\250\213\347\232\204\347\255\211\345\276\205shared_future.cpp" @@ -0,0 +1,37 @@ +#include +#include +#include + +std::string fetch_data() { + std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时操作 + return "从网络获取的数据!"; +} + +int main() { + std::future future_data = std::async(std::launch::async, fetch_data); + + // // 转移共享状态,原来的 future 被清空 valid() == false + std::shared_future shared_future_data = future_data.share(); + + // 多个线程持有一个 shared_future 对象并操作 + + // 第一个线程等待结果并访问数据 + std::thread thread1([shared_future_data] { + std::cout << "线程1:等待数据中..." << std::endl; + shared_future_data.wait(); // 等待结果可用 + std::cout << "线程1:收到数据:" << shared_future_data.get() << std::endl; + }); + + // 第二个线程等待结果并访问数据 + std::thread thread2([shared_future_data] { + std::cout << "线程2:等待数据中..." << std::endl; + shared_future_data.wait(); + std::cout << "线程2:收到数据:" << shared_future_data.get() << std::endl; + }); + + thread1.join(); + thread2.join(); + + std::promise p; + std::shared_future sf{ p.get_future() }; // 隐式转移所有权 +} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/32\351\231\220\346\227\266\347\255\211\345\276\205-\346\227\266\351\222\237.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/32\351\231\220\346\227\266\347\255\211\345\276\205-\346\227\266\351\222\237.cpp" new file mode 100644 index 00000000..d5a07cdc --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/32\351\231\220\346\227\266\347\255\211\345\276\205-\346\227\266\351\222\237.cpp" @@ -0,0 +1,14 @@ +#include +#include +#include +using namespace std::chrono_literals; + +int main(){ + auto now = std::chrono::system_clock::now(); + time_t now_time = std::chrono::system_clock::to_time_t(now); + std::cout << "Current time:\t" << std::put_time(std::localtime(&now_time), "%H:%M:%S\n"); + + auto now2 = std::chrono::steady_clock::now(); + now_time = std::chrono::system_clock::to_time_t(now); + std::cout << "Current time:\t" << std::put_time(std::localtime(&now_time), "%H:%M:%S\n"); +} \ No newline at end of file diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt b/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt index d90bdce2..4c077d57 100644 --- a/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt @@ -12,7 +12,7 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "C add_compile_options("-finput-charset=UTF-8" "-fexec-charset=UTF-8") endif() -add_executable(${PROJECT_NAME} "26使用条件变量实现后台提示音播放.cpp") +add_executable(${PROJECT_NAME} "32限时等待-时钟.cpp") # 设置 SFML 的 CMake 路径 diff --git "a/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" "b/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" index b21bbb15..9524474d 100644 --- "a/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" +++ "b/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" @@ -722,7 +722,7 @@ auto sum(ForwardIt first, ForwardIt last) { > [运行](https://godbolt.org/z/r19MYcv6e)测试。 -相比于之前,其实不同无非是定义了 `std::vector> tasks` 与 `std::vector> futures` ,然后在循环中制造任务插入容器,关联 tuple,再放到线程中执行。最后汇总的时候写一个循环,`futures[i].get()` 获取任务的返回值加起来即可。 +相比于之前,其实不同无非是定义了 `std::vector> tasks` 与 `std::vector> futures` ,然后在循环中制造任务插入容器,关联 future,再放到线程中执行。最后汇总的时候写一个循环,`futures[i].get()` 获取任务的返回值加起来即可。 到此,也就可以了。 @@ -799,7 +799,7 @@ int main() { 来自线程的异常: 一个异常 ``` -你可能对这段代码还有一些疑问:我们写的是 `promised` ,但是却没有使用 `set_value` 设置值,你可能会想着再写一行 `prom.set_value(0)`? +你可能对这段代码还有一些疑问:我们写的是 `promise` ,但是却没有使用 `set_value` 设置值,你可能会想着再写一行 `prom.set_value(0)`? 共享状态的 promise 已经存储值或者异常,再次调用 `set_value`(`set_exception`) 会抛出 [std::future_error](https://zh.cppreference.com/w/cpp/thread/future_error) 异常,将错误码设置为 [`promise_already_satisfied`](https://zh.cppreference.com/w/cpp/thread/future_errc)。这是因为 `std::promise` 对象只能是存储值或者异常其中一种,而**无法共存**。 @@ -893,13 +893,43 @@ _Ty& get() { 如果需要进行多次 `get` 调用,可以考虑使用下文提到的 `std::shared_future`。 -### 多个线程的等待 +### 多个线程的等待 `std::shared_future` -之前的例子中都在用 `std::future` ,不过 `std::future` 也有局限性。很多线程在等待的时候,只有一个线程能获取结果。当多个线程等待相同事件的结果时,就需要使用 `std::shared_future` 来替代 `std::future` 了。`std::future` 与 `std::shared_future` 的区别就如同 `std::unique_ptr`、`std::shared_ptr` 一样。 +之前的例子中我们一直使用 `std::future`,但 `std::future` 有一个局限:**future 是一次性的**,它的结果只能被一个线程获取。`get()` 成员函数只能调用一次,当结果被某个线程获取后,`std::future` 就无法再用于其他线程。 + +```cpp +int task(){ + // todo.. + return 10; +} + +void thread_functio(std::future& fut){ + // todo.. + int result = fut.get(); + std::cout << result << '\n'; + // todo.. +} + +int main(){ + auto future = std::async(task); // 启动耗时的异步任务 + + // 可能有多个线程都需要此任务的返回值,于是我们将与其关联的 future 对象的引入传入 + std::thread t{ thread_functio,std::ref(future) }; + std::thread t2{ thread_functio,std::ref(future) }; + t.join(); + t2.join(); +} +``` + +> 可能有多个线程都需要耗时的异步任务的返回值,于是我们将与其关联的 future 对象的引入传给线程对象,让它能在需要的时候获取。 +> +> 但是这存在个问题,future 是一次性的,只能被调用一次 `get()` 成员函数,所以以上代码存在问题。 + +此时就需要使用 `std::shared_future` 来替代 `std::future` 了。`std::future` 与 `std::shared_future` 的区别就如同 `std::unique_ptr`、`std::shared_ptr` 一样。 `std::future` 是只能移动的,其所有权可以在不同的对象中互相传递,但只有一个对象可以获得特定的同步结果。而 `std::shared_future` 是可复制的,多个对象可以指代同一个共享状态。 -在多个线程中对**同一个 **`std::shared_future` 对象进行操作时(如果没有进行同步保护)存在竞争条件。而从多个线程访问同一共享状态,若每个线程都是通过其自身的 `shared_future` 对象**副本**进行访问,则是安全的。 +在多个线程中对**同一个 **`std::shared_future` 对象进行操作时(如果没有进行同步保护)存在条件竞争。而从多个线程访问同一共享状态,若每个线程都是通过其自身的 `shared_future` 对象**副本**进行访问,则是安全的。 ```cpp std::string fetch_data() { @@ -932,7 +962,7 @@ int main() { } ``` -这段代码存在数据竞争,就如同我们先前所说:“***在多个线程中对**同一个 **`std::shared_future` 对象进行操作时(如果没有进行同步保护)存在竞争条件***”,它并没有提供线程安全的方式。而我们的 lambda 是按引用传递,也就是“**同一个**”进行操作了。可以改为: +这段代码存在数据竞争,就如同我们先前所说:“***在多个线程中对**同一个 **`std::shared_future` 对象进行操作时(如果没有进行同步保护)存在条件竞争***”,它并没有提供线程安全的方式。而我们的 lambda 是按引用传递,也就是“**同一个**”进行操作了。可以改为: ```cpp std::string fetch_data() { @@ -962,13 +992,13 @@ int main() { } ``` -这样访问的就都是 `std::shared_future` 的副本了,我们的 lambda 按复制捕获 std::shared_future 对象,每个线程都有一个 shared_future 的副本,这样不会有任何问题。这一点和 `std::shared_ptr` 类似[^2]。 +这样访问的就都是 `std::shared_future` 的副本了,我们的 lambda 按复制捕获 `std::shared_future` 对象,每个线程都有一个 shared_future 的副本,这样不会有任何问题。这一点和 `std::shared_ptr` 类似[^2]。 `std::promise` 也同,它的 `get_future()` 成员函数一样可以用来构造 `std::shared_future`,虽然它的返回类型是 `std::future`,不过不影响,这是因为 `std::shared_future` 有一个 `std::future&&` 参数的[构造函数](https://zh.cppreference.com/w/cpp/thread/shared_future/shared_future),转移 `std::future` 的所有权。 ```cpp -std::promisep; -std::shared_futuresf{ p.get_future() }; // 隐式转移所有权 +std::promise p; +std::shared_future sf{ p.get_future() }; // 隐式转移所有权 ``` 就不需要再强调了。 @@ -1010,7 +1040,7 @@ class duration; 如你所见,它默认的时钟节拍是 1,这是一个很重要的类,标准库通过它定义了很多的时间类型,比如 **`std::chrono::minutes`** 是分钟类型,那么它的 `Period` 就是 `std::ratio<60>` ,因为一分钟等于 60 秒。 ```cpp -std::chrono::minutes std::chrono::duration> +using minutes = duration>; ``` 稳定时钟(Steady Clock)是指提供稳定、持续递增的时间流逝信息的时钟。它的特点是不受系统时间调整或变化的影响,即使在系统休眠或时钟调整的情况下,它也能保持稳定。在 C++ 标准库中,[`std::chrono::steady_clock`](https://zh.cppreference.com/w/cpp/chrono/steady_clock) 就是一个稳定时钟。它通常用于测量时间间隔和性能计时等需要高精度和稳定性的场景。可以通过 `is_steady` 静态常量判断当前时钟是否是稳定时钟。 From 9fa4d5bdc6f065e2688d003d3f1797b451a90198 Mon Sep 17 00:00:00 2001 From: mq-b <3326284481@qq.com> Date: Mon, 23 Sep 2024 16:52:17 +0800 Subject: [PATCH 07/13] =?UTF-8?q?=E8=A1=A5=E5=85=85=20SFML=20=E5=BA=93?= =?UTF-8?q?=E7=8E=AF=E5=A2=83=E4=B8=8E=E4=BE=9D=E8=B5=96=E7=9A=84=E8=AF=B4?= =?UTF-8?q?=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...04\345\220\214\346\255\245\346\223\215\344\275\234.md" | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git "a/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" "b/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" index 9524474d..d026575c 100644 --- "a/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" +++ "b/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" @@ -411,6 +411,14 @@ enum SoundIndex { 如果是测试使用,不知道去哪生成这些语音播报,我们推荐 [`tts-vue`](https://github.com/LokerL/tts-vue)。 +> 我们的代码也可以在 Linux 中运行,并且整体仅需 C++11 标准,除了 `soundResources` 数组以外。 +> SFML 依赖于 [**FLAC**](https://xiph.org/flac/) 和 [**OpenAL**](https://www.openal.org/) 这两个库。在 Windows 上[下载](https://www.sfml-dev.org/download/sfml/2.5.1/)的 SFML 版本已包含这些依赖,但在 Linux 上需要用户自行下载并安装它们。如: +> ```shell +> sudo apt-get install libflac-dev +> sudo apt-get install libopenal-dev +> ``` + + ## 使用 `future` 举个例子:我们在车站等车,你可能会做一些别的事情打发时间,比如学习[现代 C++ 模板教程](https://github.com/Mq-b/Modern-Cpp-templates-tutorial)、观看 [mq白](https://space.bilibili.com/1292761396) 的视频教程、玩手机等。不过,你始终在等待一件事情:***车到站***。 From 89ee36de6f2b3f15c05183a408daaa141e7666e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?mq=E7=99=BD?= <3326284481@qq.com> Date: Tue, 24 Sep 2024 19:09:30 +0800 Subject: [PATCH 08/13] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=95=99=E6=A1=88=E6=8E=AA=E8=BE=9E=EF=BC=8C=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../async_progress_bar/async_progress_bar.h" | 2 +- ...-\346\227\266\351\227\264\346\256\265.cpp" | 17 ++++++++++ ...-\346\227\266\351\227\264\347\202\271.cpp" | 31 +++++++++++++++++++ .../CMakeLists.txt | 2 +- ...61\344\272\253\346\225\260\346\215\256.md" | 2 +- ...14\346\255\245\346\223\215\344\275\234.md" | 24 +++++++++++++- 6 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/33\351\231\220\346\227\266\347\255\211\345\276\205-\346\227\266\351\227\264\346\256\265.cpp" create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/34\351\231\220\346\227\266\347\255\211\345\276\205-\346\227\266\351\227\264\347\202\271.cpp" diff --git "a/code/04\345\220\214\346\255\245\346\223\215\344\275\234/async_progress_bar/async_progress_bar.h" "b/code/04\345\220\214\346\255\245\346\223\215\344\275\234/async_progress_bar/async_progress_bar.h" index f97b0ac9..c3f43d3f 100644 --- "a/code/04\345\220\214\346\255\245\346\223\215\344\275\234/async_progress_bar/async_progress_bar.h" +++ "b/code/04\345\220\214\346\255\245\346\223\215\344\275\234/async_progress_bar/async_progress_bar.h" @@ -67,5 +67,5 @@ class async_progress_bar : public QMainWindow{ QPushButton* button{}; QPushButton* button2{}; Ui::async_progress_barClass ui{}; - std::futurefuture; + std::future future; }; diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/33\351\231\220\346\227\266\347\255\211\345\276\205-\346\227\266\351\227\264\346\256\265.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/33\351\231\220\346\227\266\347\255\211\345\276\205-\346\227\266\351\227\264\346\256\265.cpp" new file mode 100644 index 00000000..550c88cc --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/33\351\231\220\346\227\266\347\255\211\345\276\205-\346\227\266\351\227\264\346\256\265.cpp" @@ -0,0 +1,17 @@ +#include +#include +#include +#include +using namespace std::chrono_literals; + +int main(){ + using namespace std::chrono; + auto future = std::async(std::launch::deferred, []{ + std::cout << "deferred\n"; + }); + + if (future.wait_for(35ms) == std::future_status::deferred) + std::cout << "future_status::deferred " << "正在延迟执行\n"; + + future.wait(); // 在 wait() 或 get() 调用时执行,不创建线程 +} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/34\351\231\220\346\227\266\347\255\211\345\276\205-\346\227\266\351\227\264\347\202\271.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/34\351\231\220\346\227\266\347\255\211\345\276\205-\346\227\266\351\227\264\347\202\271.cpp" new file mode 100644 index 00000000..de346347 --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/34\351\231\220\346\227\266\347\255\211\345\276\205-\346\227\266\351\227\264\347\202\271.cpp" @@ -0,0 +1,31 @@ +#include +#include +#include +#include +#include +#include +#pragma comment(lib,"winmm.lib") + +using namespace std::chrono_literals; + +std::condition_variable cv; +bool done{}; +std::mutex m; + +bool wait_loop() { + const auto timeout = std::chrono::steady_clock::now() + 500ms; + std::unique_lock lk{ m }; + if (!cv.wait_until(lk, timeout, [] {return done; })) { + std::cout << "超时 500ms\n"; + return false; + } + return true; +} + +int main() { + std::thread t{ wait_loop }; + std::this_thread::sleep_for(400ms); + done = true; + cv.notify_one(); + t.join(); +} diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt b/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt index 4c077d57..c46cea05 100644 --- a/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt @@ -12,7 +12,7 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "C add_compile_options("-finput-charset=UTF-8" "-fexec-charset=UTF-8") endif() -add_executable(${PROJECT_NAME} "32限时等待-时钟.cpp") +add_executable(${PROJECT_NAME} "34限时等待-时间点.cpp") # 设置 SFML 的 CMake 路径 diff --git "a/md/03\345\205\261\344\272\253\346\225\260\346\215\256.md" "b/md/03\345\205\261\344\272\253\346\225\260\346\215\256.md" index 2bd09ee0..ebc29e14 100644 --- "a/md/03\345\205\261\344\272\253\346\225\260\346\215\256.md" +++ "b/md/03\345\205\261\344\272\253\346\225\260\346\215\256.md" @@ -647,7 +647,7 @@ void process_data(){ 保护共享数据并非必须使用互斥量,互斥量只是其中一种常见的方式而已,对于一些特殊的场景,也有专门的保护方式,比如**对于共享数据的初始化过程的保护**。我们通常就不会用互斥量,**这会造成很多的额外开销**。 -我们不想为各位介绍其它乱七八糟的各种保护初始化的方式,我们只介绍三种:**双检锁(错误)**、**使用 `std::call_once`**、**静态局部变量初始化在 C++11 是线程安全**。 +我们不想为各位介绍其它乱七八糟的各种保护初始化的方式,我们只介绍三种:**双检锁(错误)**、**使用 `std::call_once`**、**静态局部变量初始化从 C++11 开始是线程安全**。 1. **双检锁(错误)线程不安全** diff --git "a/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" "b/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" index d026575c..ae6f1387 100644 --- "a/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" +++ "b/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" @@ -1182,7 +1182,7 @@ template< > class time_point; ``` -如你所见,它的第二个模板参数的时间单位,默认是根据第一个参数时钟得到的,所以假设有类型: +如你所见,它的第二个模板参数是**时间段**,就是时间的间隔,其实也就可以理解为表示时间点的**精度**,默认是根据第一个参数时钟得到的,所以假设有类型: ```cpp std::chrono::time_point @@ -1200,8 +1200,30 @@ std::chrono::time_point> // // 100 nanoseconds ``` +也就是说 `std::chrono::time_point` 的精度是 100 纳秒。 + 更多的问题参见[源码](https://github.com/microsoft/STL/blob/f54203f/stl/inc/__msvc_chrono.hpp#L644-L647)都很直观。 +> 注意,这里的精度并非是实际的时间精度。时间和硬件系统等关系极大,以 windows 为例: +> +> Windows 内核中的时间间隔计时器默认每隔 **15.6** 毫秒触发一次中断。因此,如果你使用基于系统时钟的计时方法,默认情况下精度约为 15.6 毫秒。不可能达到纳秒级别。 +> +> 由于这个系统时钟的限制,那些基于系统时钟的 API(例如 `Sleep()`、`WaitForSingleObject()` 等)的最小睡眠时间默认就是 15.6 毫秒左右。 +> +> 如: +> +> ```cpp +> std::this_thread::sleep_for(std::chrono::milliseconds(1)); +> ``` +> +> 不过我们也可以使用系统 API 调整系统时钟的精度,需要链接 windows 多媒体库 **`winmm.lib`** ,然后使用 API: +> +> ```cpp +> timeBeginPeriod(1); // 设置时钟精度为 1 毫秒 +> // todo.. +> timeEndPeriod(1); // 恢复默认精度 +> ``` + --- 同样的,时间点也支持加减以及比较操作。 From 8f46c3ff03bc0412090583c801bb0c45641bf504 Mon Sep 17 00:00:00 2001 From: mq-b <3326284481@qq.com> Date: Thu, 26 Sep 2024 14:06:25 +0800 Subject: [PATCH 09/13] =?UTF-8?q?fix=20#27=20=E6=B3=A8=E6=84=8F=E6=92=AD?= =?UTF-8?q?=E6=94=BE=E7=B1=BB=E7=9A=84=E5=88=9D=E5=A7=8B=E5=8C=96=E9=97=AE?= =?UTF-8?q?=E9=A2=98=EF=BC=8C=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" | 5 +++++ 1 file changed, 5 insertions(+) diff --git "a/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" "b/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" index ae6f1387..74f89d78 100644 --- "a/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" +++ "b/md/04\345\220\214\346\255\245\346\223\215\344\275\234.md" @@ -370,6 +370,11 @@ private: 该代码实现了一个简单的**后台音频播放类型**,通过**条件变量**和**互斥量**确保播放线程 `playMusic` 只在只在**有音频任务需要播放时工作**(当外部通过调用 `addAudioPath()` 向队列添加播放任务时)。在没有任务时,线程保持等待状态,避免占用 CPU 资源影响主程序的运行。 +> ### 注意 +> 其实这段代码还存在着一个初始化顺序导致的问题,见 [**#27**](https://github.com/Mq-b/ModernCpp-ConcurrentProgramming-Tutorial/issues/27) + +此外,关于提示音的播报,为了避免每次都手动添加路径,我们可以创建一个音频资源数组,便于使用: + 此外,关于提示音的播报,为了避免每次都手动添加路径,我们可以创建一个音频资源数组,便于使用: ```cpp From f9eed738d0b89331aecd587bc1397c996b683702 Mon Sep 17 00:00:00 2001 From: mq-b <3326284481@qq.com> Date: Fri, 27 Sep 2024 15:59:16 +0800 Subject: [PATCH 10/13] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=AC=AC=E4=BA=94?= =?UTF-8?q?=E7=AB=A0=20`std::atomic`=20=E6=8E=AA=E8=BE=9E=E4=BB=A5?= =?UTF-8?q?=E5=8F=8A=E8=A1=A5=E5=85=85=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...5\216\237\345\255\220\346\223\215\344\275\234.md" | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git "a/md/05\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216\345\216\237\345\255\220\346\223\215\344\275\234.md" "b/md/05\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216\345\216\237\345\255\220\346\223\215\344\275\234.md" index 9909bb6d..a6076eff 100644 --- "a/md/05\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216\345\216\237\345\255\220\346\223\215\344\275\234.md" +++ "b/md/05\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216\345\216\237\345\255\220\346\223\215\344\275\234.md" @@ -40,11 +40,13 @@ void f() { ### 原子类型 `std::atomic` -标准原子类型定义在头文件 [``](https://zh.cppreference.com/w/cpp/header/atomic) 中。这些类型的操作都是原子的,语言定义中只有这些类型的操作是原子的,虽然也可以用互斥量来模拟原子操作(见上文)。标准的原子的类型实现可能是:*它们几乎都有一个 `is_lock_free()` 成员函数,这个函数可以让用户查询某原子类型的操作是直接用的原子指令(返回 `true`),还是内部用了锁实现(返回 `false`)。* +标准原子类型定义在头文件 [``](https://zh.cppreference.com/w/cpp/header/atomic) 中。这些类型的操作都是原子的,语言定义中只有这些类型的操作是原子的,虽然也可以用互斥量来模拟原子操作(见上文)。 -> 每个 `std::atomic` 模板的实例化和全特化均定义一个原子类型。**如果一个线程写入原子对象,同时另一线程从它读取,那么行为有良好定义**(数据竞争的细节见[内存模型](https://zh.cppreference.com/w/cpp/language/memory_model))。 +标准原子类型的实现通常包括一个 `is_lock_free()` 成员函数,允许用户查询特定原子类型的操作是否是通过直接的原子指令实现(返回 true),还是通过锁来实现(返回 false)。 -原子操作可以代替互斥量,来进行同步操作,也能带来更高的性能。但是如果它的内部使用互斥量实现,那么不可能有性能的提升。 +> **如果一个线程写入原子对象,同时另一线程从它读取,那么行为有良好定义**(数据竞争的细节见[内存模型](https://zh.cppreference.com/w/cpp/language/memory_model))。 + +原子操作可以在一些时候代替互斥量,来进行同步操作,也能带来更高的性能。但是如果它的内部使用互斥量实现,那么不可能有性能的提升。 在 C++17 中,所有原子类型都有一个 `static constexpr` 的数据成员 [`is_always_lock_free`](https://zh.cppreference.com/w/cpp/atomic/atomic/is_always_lock_free) 。如果当前环境上的原子类型 X 是无锁类型,那么 `X::is_always_lock_free` 将返回 `true` 。例如: @@ -101,7 +103,7 @@ else { 因为 `is_always_lock_free` 是编译期常量,所以我们可以使用 C++17 引入的 `constexpr if` ,它可以在编译阶段进行决策,避免了运行时的判断开销,提高了性能。 -宏则更是简单了,最基本的预处理器判断,在预处理阶段就选择执行合适的代码。 +宏则更是简单了,最基本的预处理器判断,在预处理阶段就选择编译合适的代码。 在实际应用中,如果一个类型的原子操作总是无锁的,我们可以更放心地在性能关键的代码路径中使用它。例如,在高频交易系统、实时系统或者其它需要高并发性能的场景中,无锁的原子操作可以显著减少锁的开销和争用,提高系统的吞吐量和响应时间。 @@ -111,6 +113,8 @@ else { 2. **减少原子操作的频率**:通过批处理等技术,减少对原子操作的调用次数。 3. **使用更高效的同步机制**:在一些情况下,其它同步机制(如读写锁)可能比原子操作更高效。 +当然,其实很多时候根本没这种性能的担忧,我们很多时候使用原子对象只是为了简单方便,比如 `std::atomic` 表示状态、`std::atomic` 进行计数等。即使它们是用了锁,那也是封装好了的,起码用着方便,而不需要在代码中引入额外的互斥量来保护,更加简洁。这也是很正常的需求,各位不但要考虑程序的性能,同时也要考虑代码的简洁性、易用性。即使使用原子类型无法带来效率的提升,那也没有负提升。 + --- 除了直接使用 `std::atomic` 模板外,也可以使用原子类型的别名。这个数量非常之多,见 [MSVC STL](https://github.com/microsoft/STL/blob/daeb0a6/stl/inc/atomic#L2745-L2805)。 From acda8c69f0356cc65c4e139b5ac90b63ad2dfcf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?mq=E7=99=BD?= <3326284481@qq.com> Date: Sat, 28 Sep 2024 21:59:18 +0800 Subject: [PATCH 11/13] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BB=A5=E5=8F=8A=20CMake=20=E5=BC=95?= =?UTF-8?q?=E5=85=A5=20fmt=E3=80=81Qt=E3=80=81Boost=20=E7=9A=84=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=EF=BC=8C=E5=A2=9E=E5=8A=A0=E4=BD=BF=E7=94=A8=20OpenMp?= =?UTF-8?q?=20=E7=9A=84=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...0\344\277\241\345\217\267\351\207\217.cpp" | 40 ++++++ .../36C++20\351\227\251latch.cpp" | 21 ++++ ...7C++20\345\261\217\351\232\234barrier.cpp" | 39 ++++++ ...3\345\214\226\351\241\272\345\272\217.cpp" | 41 ++++++ ...255\220\347\261\273\345\236\213atomic.cpp" | 49 ++++++++ ...3\346\261\240\344\275\277\347\224\250.cpp" | 23 ++++ ...2\347\272\277\347\250\213\346\261\240.cpp" | 118 ++++++++++++++++++ .../CMakeLists.txt | 27 ++-- ...37\345\255\220\346\223\215\344\275\234.md" | 2 +- 9 files changed, 349 insertions(+), 11 deletions(-) create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/35C++20\344\277\241\345\217\267\351\207\217.cpp" create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/36C++20\351\227\251latch.cpp" create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/37C++20\345\261\217\351\232\234barrier.cpp" create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/38\347\254\254\345\233\233\347\253\240\346\200\273\347\273\223-\345\213\230\350\257\257\345\210\235\345\247\213\345\214\226\351\241\272\345\272\217.cpp" create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/39\345\216\237\345\255\220\347\261\273\345\236\213atomic.cpp" create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/40\347\272\277\347\250\213\346\261\240\344\275\277\347\224\250.cpp" create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/41\345\256\236\347\216\260\344\270\200\344\270\252\347\272\277\347\250\213\346\261\240.cpp" diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/35C++20\344\277\241\345\217\267\351\207\217.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/35C++20\344\277\241\345\217\267\351\207\217.cpp" new file mode 100644 index 00000000..36629670 --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/35C++20\344\277\241\345\217\267\351\207\217.cpp" @@ -0,0 +1,40 @@ +#include +#include +#include +#include +#include +using namespace std::chrono_literals; + +// 定义一个信号量,最大并发数为 3 +std::counting_semaphore<3> semaphore{ 3 }; + +void handle_request(int request_id) { + // 请求到达,尝试获取信号量 + std::cout << "进入 handle_request 尝试获取信号量\n"; + + semaphore.acquire(); + + std::cout << "成功获取信号量\n"; + + // 此处延时三秒可以方便测试,会看到先输出 3 个“成功获取信号量”,因为只有三个线程能成功调用 acquire,剩余的会被阻塞 + std::this_thread::sleep_for(3s); + + // 模拟处理时间 + std::random_device rd; + std::mt19937 gen{ rd() }; + std::uniform_int_distribution<> dis(1, 5); + int processing_time = dis(gen); + std::this_thread::sleep_for(std::chrono::seconds(processing_time)); + + std::cout << std::format("请求 {} 已被处理\n", request_id); + + semaphore.release(); +} + +int main() { + // 模拟 10 个并发请求 + std::vector threads; + for (int i = 0; i < 10; ++i) { + threads.emplace_back(handle_request, i); + } +} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/36C++20\351\227\251latch.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/36C++20\351\227\251latch.cpp" new file mode 100644 index 00000000..e7ef4efe --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/36C++20\351\227\251latch.cpp" @@ -0,0 +1,21 @@ +#include +#include +#include +#include +using namespace std::chrono_literals; + +std::latch latch{ 10 }; + +void f(int id) { + //todo.. 脑补任务 + std::cout << std::format("线程 {} 执行完任务,开始等待其它线程执行到此处\n", id); + latch.arrive_and_wait(); // 减少 并等待 count_down(1); wait(); 等待计数为 0 + std::cout << std::format("线程 {} 彻底退出函数\n", id); +} + +int main() { + std::vector threads; + for (int i = 0; i < 10; ++i) { + threads.emplace_back(f, i); + } +} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/37C++20\345\261\217\351\232\234barrier.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/37C++20\345\261\217\351\232\234barrier.cpp" new file mode 100644 index 00000000..d8e3e1a7 --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/37C++20\345\261\217\351\232\234barrier.cpp" @@ -0,0 +1,39 @@ +#include +#include +#include +#include + +void f(int start, int end, int thread_id) { + for (int i = start; i <= end; ++i) { + // 输出当前线程的数字 + std::cout << std::to_string(i) + " "; + + // 等待所有线程同步到达 barrier 也就是等待都输出完数字 +#pragma omp barrier + +// 每个线程输出完一句后,主线程输出轮次信息 +#pragma omp master + { + static int round_number = 1; + std::cout << "\t第" << round_number++ << "轮结束\n"; + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + // 再次同步 等待所有线程(包括主线程)到达此处、避免其它线程继续执行打断主线程的输出 +#pragma omp barrier + } +} + +int main() { + constexpr int num_threads = 10; + omp_set_num_threads(num_threads); + +#pragma omp parallel + { + const int thread_id = omp_get_thread_num(); + f(thread_id * 10 + 1, (thread_id + 1) * 10, thread_id); + } + +} + +// https://godbolt.org/z/fabqhbx3P \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/38\347\254\254\345\233\233\347\253\240\346\200\273\347\273\223-\345\213\230\350\257\257\345\210\235\345\247\213\345\214\226\351\241\272\345\272\217.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/38\347\254\254\345\233\233\347\253\240\346\200\273\347\273\223-\345\213\230\350\257\257\345\210\235\345\247\213\345\214\226\351\241\272\345\272\217.cpp" new file mode 100644 index 00000000..6c86778a --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/38\347\254\254\345\233\233\347\253\240\346\200\273\347\273\223-\345\213\230\350\257\257\345\210\235\345\247\213\345\214\226\351\241\272\345\272\217.cpp" @@ -0,0 +1,41 @@ +#include +#include +#include + +struct X { + X() { + // 假设 X 的初始化没那么快 + std::this_thread::sleep_for(std::chrono::seconds(1)); + std::puts("X"); + v.resize(10, 6); + } + std::vector v; +}; + +struct Test { + Test()/* : t{ &Test::f, this }*/ // 线程已经开始执行 + { + // 严格意义来说 这里不算初始化 至少不算 C++ 标准的定义 + } + void start() + { + t = std::thread{ &Test::f, this }; + } + ~Test() { + if (t.joinable()) + t.join(); + } + void f()const { // 如果在函数执行的线程 f 中使用 x 则会存在问题。使用了未初始化的数据成员 ub + std::cout << "f\n"; + std::cout << x.v[9] << '\n'; + } + + + std::thread t; // 声明顺序决定了初始化顺序,优先初始化 t + X x; +}; + +int main() { + Test t; + t.start(); +} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/39\345\216\237\345\255\220\347\261\273\345\236\213atomic.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/39\345\216\237\345\255\220\347\261\273\345\236\213atomic.cpp" new file mode 100644 index 00000000..91afb9f5 --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/39\345\216\237\345\255\220\347\261\273\345\236\213atomic.cpp" @@ -0,0 +1,49 @@ +#include +#include +#include +#include +using namespace std::chrono_literals; + +// 不要这样使用 不要在多线程并发中使用 volatile +// 它的行为是不保证的 +std::atomic n = 0; + +void read(){ + while(true){ + std::this_thread::sleep_for(500ms); + std::cout << n.load() << '\n'; + } +} + +void write(){ + while (true){ + ++n; + } +} + +// 数据竞争 数据竞争未定义行为 +// 优化会假设你的程序中没有未定义行为 + +// C 语言的平凡的结构体 +struct trivial_type { + int x; + float y; +}; + +int main(){ + // 创建一个 std::atomic 对象 + std::atomic atomic_my_type{ { 10, 20.5f } }; + + // 使用 store 和 load 操作来设置和获取值 + trivial_type new_value{ 30, 40.5f }; + atomic_my_type.store(new_value); + + std::cout << "x: " << atomic_my_type.load().x << ", y: " << atomic_my_type.load().y << std::endl; + + // 使用 exchange 操作 + trivial_type exchanged_value = atomic_my_type.exchange({ 50, 60.5f }); + std::cout << "交换前的 x: " << exchanged_value.x + << ", 交换前的 y: " << exchanged_value.y << std::endl; + std::cout << "交换后的 x: " << atomic_my_type.load().x + << ", 交换后的 y: " << atomic_my_type.load().y << std::endl; +} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/40\347\272\277\347\250\213\346\261\240\344\275\277\347\224\250.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/40\347\272\277\347\250\213\346\261\240\344\275\277\347\224\250.cpp" new file mode 100644 index 00000000..d8898ba7 --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/40\347\272\277\347\250\213\346\261\240\344\275\277\347\224\250.cpp" @@ -0,0 +1,23 @@ +#include +#include +#include +#include + +int main(int argc, char* argv[]) { + QCoreApplication app(argc, argv); + + QThreadPool* threadPool = QThreadPool::globalInstance(); + + // 线程池最大线程数 + qDebug() << threadPool->maxThreadCount(); + + for (int i = 0; i < 20; ++i) { + threadPool->start([i]{ + qDebug() << QString("thread id %1").arg(i); + }); + } + // 当前活跃线程数 10 + qDebug() << threadPool->activeThreadCount(); + + app.exec(); +} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/41\345\256\236\347\216\260\344\270\200\344\270\252\347\272\277\347\250\213\346\261\240.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/41\345\256\236\347\216\260\344\270\200\344\270\252\347\272\277\347\250\213\346\261\240.cpp" new file mode 100644 index 00000000..091ca19f --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/41\345\256\236\347\216\260\344\270\200\344\270\252\347\272\277\347\250\213\346\261\240.cpp" @@ -0,0 +1,118 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std::chrono_literals; + +inline std::size_t default_thread_pool_size() noexcept{ + std::size_t num_threads = std::thread::hardware_concurrency(); + num_threads = num_threads == 0 ? 2 : num_threads; // 防止无法检测当前硬件,让我们线程池至少有 2 个线程 + return num_threads; +} + +class ThreadPool{ +public: + using Task = std::packaged_task; + + ThreadPool(const ThreadPool&) = delete; + ThreadPool& operator=(const ThreadPool&) = delete; + + ThreadPool(std::size_t num_thread = default_thread_pool_size()) : + stop_{ false }, num_thread_{ num_thread } + { + start(); + } + ~ThreadPool(){ + stop(); + } + + void stop(){ + stop_ = true; + cv_.notify_all(); + for (auto& thread : pool_){ + if (thread.joinable()) + thread.join(); + } + pool_.clear(); + } + + template + std::future, std::decay_t...>> submit(F&& f, Args&&...args){ + using RetType = std::invoke_result_t, std::decay_t...>; + if(stop_){ + throw std::runtime_error("ThreadPool is stopped"); + } + auto task = std::make_shared>(std::bind(std::forward(f), std::forward(args)...)); + + std::future ret = task->get_future(); + + { + std::lock_guard lc{ mutex_ }; + tasks_.emplace([task] {(*task)(); }); + } + cv_.notify_one(); + + return ret; + } + + void start(){ + for (std::size_t i = 0; i < num_thread_; ++i){ + pool_.emplace_back([this]{ + while (!stop_) { + Task task; + { + std::unique_lock lock{ mutex_ }; + cv_.wait(lock, [this] {return stop_ || !tasks_.empty(); }); + if (tasks_.empty()) return; + task = std::move(tasks_.front()); + tasks_.pop(); + } + task(); + } + }); + } + } + +private: + std::mutex mutex_; + std::condition_variable cv_; + std::atomic stop_; + std::atomic num_thread_; + std::queue tasks_; + std::vector pool_; +}; + +int print_task(int n) { + std::osyncstream{ std::cout } << "Task " << n << " is running on thr: " << + std::this_thread::get_id() << '\n'; + return n; +} +int print_task2(int n) { + std::osyncstream{ std::cout } << "🐢🐢🐢 " << n << " 🐉🐉🐉" << std::endl; + return n; +} + +struct X { + void f(const int& n) const { + std::osyncstream{ std::cout } << &n << '\n'; + } +}; + +int main() { + ThreadPool pool{ 4 }; // 创建一个有 4 个线程的线程池 + + X x; + int n = 6; + std::cout << &n << '\n'; + auto t = pool.submit(&X::f, &x, n); // 默认复制,地址不同 + auto t2 = pool.submit(&X::f, &x, std::ref(n)); + t.wait(); + t2.wait(); +} // 析构自动 stop()自动 stop() \ No newline at end of file diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt b/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt index c46cea05..594a37dd 100644 --- a/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt @@ -2,24 +2,31 @@ cmake_minimum_required (VERSION 3.8) project ("ModernCpp-ConcurrentProgramming-Tutorial") -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) if(MSVC) - add_compile_options("/utf-8" "/permissive-" "/Zc:nrvo") + add_compile_options("/utf-8" "/permissive-" "/Zc:nrvo" "/openmp") elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - add_compile_options("-finput-charset=UTF-8" "-fexec-charset=UTF-8") + add_compile_options("-finput-charset=UTF-8" "-fexec-charset=UTF-8" "-fopenmp") endif() -add_executable(${PROJECT_NAME} "34限时等待-时间点.cpp") +add_executable(${PROJECT_NAME} "41实现一个线程池.cpp") - -# 设置 SFML 的 CMake 路径 set(SFML_DIR "D:/lib/SFML-2.6.1-windows-vc17-64-bit/SFML-2.6.1/lib/cmake/SFML") - -# 查找 SFML 库 find_package(SFML 2.6.1 COMPONENTS system window graphics audio network REQUIRED) +target_link_libraries(${PROJECT_NAME} PRIVATE sfml-system sfml-window sfml-graphics sfml-audio sfml-network) + +set(fmt_DIR "D:/lib/fmt_x64-windows/share/fmt") +find_package(fmt CONFIG REQUIRED) +target_link_libraries(${PROJECT_NAME} PRIVATE fmt::fmt-header-only) + +find_package(Qt6 REQUIRED Widgets) +target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Widgets) -# 链接 SFML 库到项目 设置链接选项 -target_link_libraries(${PROJECT_NAME} sfml-system sfml-window sfml-graphics sfml-audio sfml-network) +# 当前环境可以直接查找到 vcpkg 的 Boost_DIR 但是却无法查找到 include 路径,手动设置 +set(Boost_INCLUDE_DIR "D:/vcpkg-master/installed/x64-windows/include") +include_directories(${Boost_INCLUDE_DIR}) +find_package(Boost REQUIRED COMPONENTS system) +target_link_libraries(${PROJECT_NAME} PRIVATE Boost::system) diff --git "a/md/05\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216\345\216\237\345\255\220\346\223\215\344\275\234.md" "b/md/05\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216\345\216\237\345\255\220\346\223\215\344\275\234.md" index a6076eff..0075bdcc 100644 --- "a/md/05\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216\345\216\237\345\255\220\346\223\215\344\275\234.md" +++ "b/md/05\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216\345\216\237\345\255\220\346\223\215\344\275\234.md" @@ -105,7 +105,7 @@ else { 宏则更是简单了,最基本的预处理器判断,在预处理阶段就选择编译合适的代码。 -在实际应用中,如果一个类型的原子操作总是无锁的,我们可以更放心地在性能关键的代码路径中使用它。例如,在高频交易系统、实时系统或者其它需要高并发性能的场景中,无锁的原子操作可以显著减少锁的开销和争用,提高系统的吞吐量和响应时间。 +在实际应用中,如果一个类型的原子操作总是无锁的,我们可以更放心地在性能关键的代码路径中使用它。例如,在高频交易系统、实时系统或者其它需要高并发性能的场景中,无锁的原子操作可以显著减少锁的开销和竞争,提高系统的吞吐量和响应时间。 另一方面,如果发现某些原子类型在目标平台上是有锁的,我们可以考虑以下优化策略: From 7877d0e2f7e9a2ef20f37c6195079208a40f4d85 Mon Sep 17 00:00:00 2001 From: mq-b <3326284481@qq.com> Date: Sun, 29 Sep 2024 16:49:31 +0800 Subject: [PATCH 12/13] =?UTF-8?q?=E8=A1=A5=E5=85=85=E7=AC=AC=E4=BA=94?= =?UTF-8?q?=E7=AB=A0=20`std::atomic=5Fflag`=20=E7=9A=84=E6=8F=8F=E8=BF=B0?= =?UTF-8?q?=EF=BC=8C=E8=87=AA=E6=97=8B=E9=94=81=E4=B8=8E=E4=BA=92=E6=96=A5?= =?UTF-8?q?=E9=94=81=E4=BC=98=E5=8A=A3=E5=AF=B9=E6=AF=94=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\216\345\216\237\345\255\220\346\223\215\344\275\234.md" | 6 ++++++ 1 file changed, 6 insertions(+) diff --git "a/md/05\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216\345\216\237\345\255\220\346\223\215\344\275\234.md" "b/md/05\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216\345\216\237\345\255\220\346\223\215\344\275\234.md" index 0075bdcc..f3bc0615 100644 --- "a/md/05\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216\345\216\237\345\255\220\346\223\215\344\275\234.md" +++ "b/md/05\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216\345\216\237\345\255\220\346\223\215\344\275\234.md" @@ -275,6 +275,12 @@ bool r = f.test_and_set(); 有限的特性使得 `std::atomic_flag` 非常适合用作制作**自旋锁**。 +> 自旋锁可以理解为一种***忙等锁***,因为它在等待锁的过程中不会主动放弃 CPU,而是持续检查锁的状态。 +> +> 与此相对,`std::mutex` 互斥量是一种***睡眠锁***。当线程请求锁(`lock()`)而未能获取时,它会放弃 CPU 时间片,让其他线程得以执行,从而有效利用系统资源。 +> +> 从性能上看,自旋锁的响应更快,但是睡眠锁更加节省资源,高效。 + ```cpp class spinlock_mutex { std::atomic_flag flag{}; From 01aafdeb0ee94ce76d6ccc4f3000a08a10edf762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?mq=E7=99=BD?= <3326284481@qq.com> Date: Mon, 30 Sep 2024 19:07:23 +0800 Subject: [PATCH 13/13] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...0\350\207\252\346\227\213\351\224\201.cpp" | 39 ++++++++++ .../43atomic_bool.cpp | 25 ++++++ ...7\351\222\210\347\211\271\345\214\226.cpp" | 32 ++++++++ ...220\347\211\271\345\214\226shared_ptr.cpp" | 78 +++++++++++++++++++ .../CMakeLists.txt | 2 +- ...37\345\255\220\346\223\215\344\275\234.md" | 2 +- 6 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/42atomic_flag\345\256\236\347\216\260\350\207\252\346\227\213\351\224\201.cpp" create mode 100644 code/ModernCpp-ConcurrentProgramming-Tutorial/43atomic_bool.cpp create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/44atomic\346\214\207\351\222\210\347\211\271\345\214\226.cpp" create mode 100644 "code/ModernCpp-ConcurrentProgramming-Tutorial/45\345\216\237\345\255\220\347\211\271\345\214\226shared_ptr.cpp" diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/42atomic_flag\345\256\236\347\216\260\350\207\252\346\227\213\351\224\201.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/42atomic_flag\345\256\236\347\216\260\350\207\252\346\227\213\351\224\201.cpp" new file mode 100644 index 00000000..b6d9cca1 --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/42atomic_flag\345\256\236\347\216\260\350\207\252\346\227\213\351\224\201.cpp" @@ -0,0 +1,39 @@ +#include +#include +#include +#include + +class spinlock_mutex { + std::atomic_flag flag{}; +public: + spinlock_mutex()noexcept = default; + void lock()noexcept { + while (flag.test_and_set(std::memory_order_acquire)); + } + + void unlock()noexcept { + flag.clear(std::memory_order_release); + } +}; + +spinlock_mutex m; + +void f() { + std::lock_guard lc{ m }; + std::cout << "😅😅" << "❤️❤️\n"; +} + +int main(){ + std::thread t{ f }; + std::thread t1{ f }; + std::thread t2{ f }; + std::thread t3{ f }; + std::thread t4{ f }; + std::thread t5{ f }; + t.join(); + t1.join(); + t2.join(); + t3.join(); + t4.join(); + t5.join(); +} \ No newline at end of file diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/43atomic_bool.cpp b/code/ModernCpp-ConcurrentProgramming-Tutorial/43atomic_bool.cpp new file mode 100644 index 00000000..ae7f1c94 --- /dev/null +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/43atomic_bool.cpp @@ -0,0 +1,25 @@ +#include +#include +#include + +std::atomic flag{ false }; +bool expected = false; + +void try_set_flag() { + // 尝试将 flag 设置为 true,如果当前值为 false + if (flag.compare_exchange_strong(expected, true)) { + std::cout << "flag 为 false,flag 设为 true。\n"; + } + else { + std::cout << "flag 为 true, expected 设为 true。\n"; + } +} + +int main() { + std::thread t1{ try_set_flag }; + std::thread t2{ try_set_flag }; + t1.join(); + t2.join(); + std::cout << "flag: " << std::boolalpha << flag << '\n'; + std::cout << "expected: " << std::boolalpha << expected << '\n'; +} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/44atomic\346\214\207\351\222\210\347\211\271\345\214\226.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/44atomic\346\214\207\351\222\210\347\211\271\345\214\226.cpp" new file mode 100644 index 00000000..644e566b --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/44atomic\346\214\207\351\222\210\347\211\271\345\214\226.cpp" @@ -0,0 +1,32 @@ +#include + +struct X{ + int v{}; + void f()const { + std::cout << v << '\n'; + } +}; + +int main(){ + int arr[10]{ 1,2 }; + + std::atomic p{ arr }; + + p.fetch_add(1); + std::cout << *(p.load()) << '\n'; + + p.fetch_sub(1); + std::cout << *(p.load()) << '\n'; + + p += 1; + std::cout << *(p.load()) << '\n'; + + p -= 1; + std::cout << *(p.load()) << '\n'; + + X xs[3]{ {10},{20},{30} }; + std::atomic p2{ xs }; + p2.load()->f(); + p2 += 2; + p2.load()->f(); +} \ No newline at end of file diff --git "a/code/ModernCpp-ConcurrentProgramming-Tutorial/45\345\216\237\345\255\220\347\211\271\345\214\226shared_ptr.cpp" "b/code/ModernCpp-ConcurrentProgramming-Tutorial/45\345\216\237\345\255\220\347\211\271\345\214\226shared_ptr.cpp" new file mode 100644 index 00000000..e050eda8 --- /dev/null +++ "b/code/ModernCpp-ConcurrentProgramming-Tutorial/45\345\216\237\345\255\220\347\211\271\345\214\226shared_ptr.cpp" @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include +using namespace std::chrono_literals; + +class Data { +public: + Data(int value = 0) : value_(value) {} + int get_value() const { return value_; } + void set_value(int new_value) { value_ = new_value; } +private: + int value_; +}; + +std::atomic> data = std::make_shared(); + +void writer() { + for (int i = 0; i < 10; ++i) { + std::shared_ptr new_data = std::make_shared(i); + data.store(new_data); + std::this_thread::sleep_for(100ms); + } +} + +void reader() { + for (int i = 0; i < 10; ++i) { + if (auto sp = data.load()) { + std::cout << "读取线程值: " << sp->get_value() << std::endl; + } + else { + std::cout << "没有读取到数据" << std::endl; + } + std::this_thread::sleep_for(100ms); + } +} + +std::atomic> ptr = std::make_shared(); + +void wait_for_wake_up() { + std::osyncstream{ std::cout } + << "线程 " + << std::this_thread::get_id() + << " 阻塞,等待更新唤醒\n"; + + // 等待 ptr 变为其它值 + ptr.wait(ptr.load()); + + std::osyncstream{ std::cout } + << "线程 " + << std::this_thread::get_id() + << " 已被唤醒\n"; +} + +void wake_up() { + std::this_thread::sleep_for(5s); + + // 更新值并唤醒 + ptr.store(std::make_shared(10)); + ptr.notify_one(); +} + +int main() { + //std::thread writer_thread{ writer }; + //std::thread reader_thread{ reader }; + + //writer_thread.join(); + //reader_thread.join(); + + //std::atomic> ptr = std::make_shared(10); + //std::atomic_ref ref{ *ptr.load() }; + //ref = 100; // 原子地赋 100 给被引用的对象 + //std::cout << *ptr.load() << '\n'; + std::thread t1{ wait_for_wake_up }; + wake_up(); + t1.join(); +} \ No newline at end of file diff --git a/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt b/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt index 594a37dd..7f2eafa3 100644 --- a/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt +++ b/code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt @@ -12,7 +12,7 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "C add_compile_options("-finput-charset=UTF-8" "-fexec-charset=UTF-8" "-fopenmp") endif() -add_executable(${PROJECT_NAME} "41实现一个线程池.cpp") +add_executable(${PROJECT_NAME} "45原子特化shared_ptr.cpp") set(SFML_DIR "D:/lib/SFML-2.6.1-windows-vc17-64-bit/SFML-2.6.1/lib/cmake/SFML") find_package(SFML 2.6.1 COMPONENTS system window graphics audio network REQUIRED) diff --git "a/md/05\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216\345\216\237\345\255\220\346\223\215\344\275\234.md" "b/md/05\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216\345\216\237\345\255\220\346\223\215\344\275\234.md" index f3bc0615..94e41e85 100644 --- "a/md/05\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216\345\216\237\345\255\220\346\223\215\344\275\234.md" +++ "b/md/05\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216\345\216\237\345\255\220\346\223\215\344\275\234.md" @@ -311,7 +311,7 @@ void f(){ 稍微聊一下原理,我们的 `spinlock_mutex` 对象中存储的 `flag` 对象在默认构造时是清除 (`false`) 状态。在 `lock()` 函数中调用 `test_and_set` 函数,它是原子的,只有一个线程能成功调用并将 `flag` 的状态原子地更改为设置 (`true`),并返回它先前的值 (`false`)。此时,该线程成功获取了锁,退出循环。 -当 `flag` 对象的状态为设置 (`true`) 时,其线程调用 `test_and_set` 函数会返回 `true`,导致它们继续在循环中自旋,无法退出。直到先前持有锁的线程调用 `unlock()` 函数,将 `flag` 对象的状态原子地更改为清除 (`false`) 状态。此时,等待的线程中会有一个线程成功调用 `test_and_set` 返回 `false`,然后退出循环,成功获取锁。 +当 `flag` 对象的状态为设置 (`true`) 时,其它线程调用 `test_and_set` 函数会返回 `true`,导致它们继续在循环中自旋,无法退出。直到先前持有锁的线程调用 `unlock()` 函数,将 `flag` 对象的状态原子地更改为清除 (`false`) 状态。此时,等待的线程中会有一个线程成功调用 `test_and_set` 返回 `false`,然后退出循环,成功获取锁。 > 值得注意的是,我们只是稍微的讲一下使用 `std::atomic_flag` 实现自旋锁。不过并不推荐各位在实践中使用它,具体可参见 [**Linus Torvalds**](https://en.wikipedia.org/wiki/Linus_Torvalds) 的[文章](https://www.realworldtech.com/forum/?threadid=189711&curpostid=189723)。其中有一段话说得很直接: >