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] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=A7=86=E9=A2=91=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=EF=BC=8C=E4=BF=AE=E6=94=B9=E7=AC=AC=E5=9B=9B=E7=AB=A0?= =?UTF-8?q?=E6=8E=AA=E8=BE=9E=E3=80=81=E6=A0=BC=E5=BC=8F=E3=80=81=E4=BB=A5?= =?UTF-8?q?=E5=8F=8A=E8=A1=A5=E5=85=85=E4=BB=A3=E7=A0=81=E7=A4=BA=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` 静态常量判断当前时钟是否是稳定时钟。