Skip to content

Commit de1f91a

Browse files
committed
更新视频代码,修改第四章措辞、格式、以及补充代码示例
1 parent 8954e3c commit de1f91a

8 files changed

+222
-11
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#include <iostream>
2+
#include <thread>
3+
#include <future> // 引入 future 头文件
4+
5+
void f() {
6+
std::cout << std::this_thread::get_id() << '\n';
7+
}
8+
9+
int main() {
10+
auto t = std::async([] {});
11+
std::future<void> future{ std::move(t) };
12+
future.wait(); // Error! 抛出异常
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#include <iostream>
2+
#include <thread>
3+
#include <future>
4+
5+
template<typename R, typename...Ts, typename...Args>
6+
void async_task(std::packaged_task<R(Ts...)>& task, Args&&...args) {
7+
// todo..
8+
task(std::forward<Args>(args)...);
9+
}
10+
11+
int main() {
12+
std::packaged_task<int(int, int)> task([](int a, int b) {
13+
return a + b;
14+
});
15+
16+
int value = 50;
17+
18+
std::future<int> future = task.get_future();
19+
20+
// 创建一个线程来执行异步任务
21+
std::thread t{ [&] { async_task(task, value, value); } };
22+
std::cout << future.get() << '\n';
23+
t.join();
24+
}
25+
26+
//int main(){
27+
// std::cout << "main: " << std::this_thread::get_id() << '\n';
28+
//
29+
// // 只能移动不能复制
30+
// std::packaged_task<double(int, int)> task{ [](int a, int b) {
31+
// std::cout << "packaged_task: " << std::this_thread::get_id() << '\n';
32+
// return std::pow(a, b);
33+
// } };
34+
//
35+
// std::future<double> future = task.get_future();
36+
//
37+
// // task(10, 2); // 调用 此处执行任务
38+
//
39+
// std::thread t{ std::move(task) ,10,2 };
40+
//
41+
// std::cout << "------\n";
42+
//
43+
// std::cout << future.get() << '\n'; // 会阻塞,直到任务执行完毕
44+
//
45+
// t.join();
46+
//}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#include <iostream>
2+
#include <thread>
3+
#include <future>
4+
#include <chrono>
5+
using namespace std::chrono_literals;
6+
7+
void f(std::promise<int> obj ,int num){
8+
// todo..
9+
obj.set_value(num * num); // 调用了 set_value
10+
// todo..
11+
std::this_thread::sleep_for(5s); // 模拟一些计算
12+
}
13+
14+
void throw_function(std::promise<int> prom) {
15+
prom.set_value(100);
16+
try {
17+
// todo..
18+
throw std::runtime_error("一个异常");
19+
}
20+
catch (...) {
21+
try {
22+
// 共享状态的 promise 已存储值,调用 set_exception 产生异常
23+
prom.set_exception(std::current_exception());
24+
}
25+
catch (std::exception& e) {
26+
std::cerr << "来自 set_exception 的异常: " << e.what() << '\n';
27+
}
28+
}
29+
}
30+
31+
int main() {
32+
std::promise<int> prom;
33+
std::future<int> fut = prom.get_future();
34+
35+
std::thread t(throw_function, std::move(prom));
36+
37+
std::cout << "等待线程执行,抛出异常并设置\n";
38+
std::cout << "值:" << fut.get() << '\n'; // 100
39+
40+
t.join();
41+
}
42+
43+
44+
//int main(){
45+
// std::promise<int> promise;
46+
//
47+
// auto future = promise.get_future(); // 关联了
48+
//
49+
// std::thread t{ f,std::move(promise), 10 };
50+
// // f(std::move(promise), 10);
51+
//
52+
// std::cout << future.get() << '\n'; // 阻塞,直至结果可用
53+
// std::cout << "end\n";
54+
// t.join();
55+
//}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#include <iostream>
2+
#include <thread>
3+
#include <future>
4+
5+
int main(){
6+
std::future<void>future = std::async([] {});
7+
std::cout << std::boolalpha << future.valid() << '\n'; // true
8+
future.get();
9+
std::cout << std::boolalpha << future.valid() << '\n'; // false
10+
try {
11+
future.get(); // 抛出 future_errc::no_state 异常
12+
}
13+
catch (std::exception& e) {
14+
std::cerr << e.what() << '\n';
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#include <iostream>
2+
#include <thread>
3+
#include <future>
4+
5+
std::string fetch_data() {
6+
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时操作
7+
return "从网络获取的数据!";
8+
}
9+
10+
int main() {
11+
std::future<std::string> future_data = std::async(std::launch::async, fetch_data);
12+
13+
// // 转移共享状态,原来的 future 被清空 valid() == false
14+
std::shared_future<std::string> shared_future_data = future_data.share();
15+
16+
// 多个线程持有一个 shared_future 对象并操作
17+
18+
// 第一个线程等待结果并访问数据
19+
std::thread thread1([shared_future_data] {
20+
std::cout << "线程1:等待数据中..." << std::endl;
21+
shared_future_data.wait(); // 等待结果可用
22+
std::cout << "线程1:收到数据:" << shared_future_data.get() << std::endl;
23+
});
24+
25+
// 第二个线程等待结果并访问数据
26+
std::thread thread2([shared_future_data] {
27+
std::cout << "线程2:等待数据中..." << std::endl;
28+
shared_future_data.wait();
29+
std::cout << "线程2:收到数据:" << shared_future_data.get() << std::endl;
30+
});
31+
32+
thread1.join();
33+
thread2.join();
34+
35+
std::promise<std::string> p;
36+
std::shared_future<std::string> sf{ p.get_future() }; // 隐式转移所有权
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#include <chrono>
2+
#include <iostream>
3+
#include <iomanip>
4+
using namespace std::chrono_literals;
5+
6+
int main(){
7+
auto now = std::chrono::system_clock::now();
8+
time_t now_time = std::chrono::system_clock::to_time_t(now);
9+
std::cout << "Current time:\t" << std::put_time(std::localtime(&now_time), "%H:%M:%S\n");
10+
11+
auto now2 = std::chrono::steady_clock::now();
12+
now_time = std::chrono::system_clock::to_time_t(now);
13+
std::cout << "Current time:\t" << std::put_time(std::localtime(&now_time), "%H:%M:%S\n");
14+
}

Diff for: code/ModernCpp-ConcurrentProgramming-Tutorial/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "C
1212
add_compile_options("-finput-charset=UTF-8" "-fexec-charset=UTF-8")
1313
endif()
1414

15-
add_executable(${PROJECT_NAME} "26使用条件变量实现后台提示音播放.cpp")
15+
add_executable(${PROJECT_NAME} "32限时等待-时钟.cpp")
1616

1717

1818
# 设置 SFML 的 CMake 路径

Diff for: md/04同步操作.md

+40-10
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,7 @@ auto sum(ForwardIt first, ForwardIt last) {
722722
723723
> [运行](https://godbolt.org/z/r19MYcv6e)测试。
724724
725-
相比于之前,其实不同无非是定义了 `std::vector<std::packaged_task<value_type()>> tasks` 与 `std::vector<std::future<value_type>> futures` ,然后在循环中制造任务插入容器,关联 tuple,再放到线程中执行。最后汇总的时候写一个循环,`futures[i].get()` 获取任务的返回值加起来即可。
725+
相比于之前,其实不同无非是定义了 `std::vector<std::packaged_task<value_type()>> tasks` 与 `std::vector<std::future<value_type>> futures` ,然后在循环中制造任务插入容器,关联 future,再放到线程中执行。最后汇总的时候写一个循环,`futures[i].get()` 获取任务的返回值加起来即可。
726726
727727
到此,也就可以了。
728728
@@ -799,7 +799,7 @@ int main() {
799799
来自线程的异常: 一个异常
800800
```
801801
802-
你可能对这段代码还有一些疑问:我们写的是 `promised<int>` ,但是却没有使用 `set_value` 设置值,你可能会想着再写一行 `prom.set_value(0)`
802+
你可能对这段代码还有一些疑问:我们写的是 `promise<int>` ,但是却没有使用 `set_value` 设置值,你可能会想着再写一行 `prom.set_value(0)`
803803
804804
共享状态的 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` 对象只能是存储值或者异常其中一种,而**无法共存**
805805
@@ -893,13 +893,43 @@ _Ty& get() {
893893
894894
如果需要进行多次 `get` 调用,可以考虑使用下文提到的 `std::shared_future`
895895
896-
### 多个线程的等待
896+
### 多个线程的等待 `std::shared_future`
897897
898-
之前的例子中都在用 `std::future` ,不过 `std::future` 也有局限性。很多线程在等待的时候,只有一个线程能获取结果。当多个线程等待相同事件的结果时,就需要使用 `std::shared_future` 来替代 `std::future` 了。`std::future``std::shared_future` 的区别就如同 `std::unique_ptr``std::shared_ptr` 一样。
898+
之前的例子中我们一直使用 `std::future`,但 `std::future` 有一个局限:**future 是一次性的**,它的结果只能被一个线程获取。`get()` 成员函数只能调用一次,当结果被某个线程获取后,`std::future` 就无法再用于其他线程。
899+
900+
```cpp
901+
int task(){
902+
// todo..
903+
return 10;
904+
}
905+
906+
void thread_functio(std::future<int>& fut){
907+
// todo..
908+
int result = fut.get();
909+
std::cout << result << '\n';
910+
// todo..
911+
}
912+
913+
int main(){
914+
auto future = std::async(task); // 启动耗时的异步任务
915+
916+
// 可能有多个线程都需要此任务的返回值,于是我们将与其关联的 future 对象的引入传入
917+
std::thread t{ thread_functio,std::ref(future) };
918+
std::thread t2{ thread_functio,std::ref(future) };
919+
t.join();
920+
t2.join();
921+
}
922+
```
923+
924+
> 可能有多个线程都需要耗时的异步任务的返回值,于是我们将与其关联的 future 对象的引入传给线程对象,让它能在需要的时候获取。
925+
>
926+
> 但是这存在个问题,future 是一次性的,只能被调用一次 `get()` 成员函数,所以以上代码存在问题。
927+
928+
此时就需要使用 `std::shared_future` 来替代 `std::future` 了。`std::future` 与 `std::shared_future` 的区别就如同 `std::unique_ptr`、`std::shared_ptr` 一样。
899929
900930
`std::future` 是只能移动的,其所有权可以在不同的对象中互相传递,但只有一个对象可以获得特定的同步结果。而 `std::shared_future` 是可复制的,多个对象可以指代同一个共享状态。
901931
902-
在多个线程中对**同一个 **`std::shared_future` 对象进行操作时(如果没有进行同步保护)存在竞争条件。而从多个线程访问同一共享状态,若每个线程都是通过其自身的 `shared_future` 对象**副本**进行访问,则是安全的。
932+
在多个线程中对**同一个 **`std::shared_future` 对象进行操作时(如果没有进行同步保护)存在条件竞争。而从多个线程访问同一共享状态,若每个线程都是通过其自身的 `shared_future` 对象**副本**进行访问,则是安全的。
903933
904934
```cpp
905935
std::string fetch_data() {
@@ -932,7 +962,7 @@ int main() {
932962
}
933963
```
934964
935-
这段代码存在数据竞争,就如同我们先前所说:“***在多个线程中对**同一个 **`std::shared_future` 对象进行操作时(如果没有进行同步保护)存在竞争条件***”,它并没有提供线程安全的方式。而我们的 lambda 是按引用传递,也就是“**同一个**”进行操作了。可以改为:
965+
这段代码存在数据竞争,就如同我们先前所说:“***在多个线程中对**同一个 **`std::shared_future` 对象进行操作时(如果没有进行同步保护)存在条件竞争***”,它并没有提供线程安全的方式。而我们的 lambda 是按引用传递,也就是“**同一个**”进行操作了。可以改为:
936966
937967
```cpp
938968
std::string fetch_data() {
@@ -962,13 +992,13 @@ int main() {
962992
}
963993
```
964994
965-
这样访问的就都是 `std::shared_future` 的副本了,我们的 lambda 按复制捕获 std::shared_future 对象,每个线程都有一个 shared_future 的副本,这样不会有任何问题。这一点和 `std::shared_ptr` 类似[^2]
995+
这样访问的就都是 `std::shared_future` 的副本了,我们的 lambda 按复制捕获 `std::shared_future` 对象,每个线程都有一个 shared_future 的副本,这样不会有任何问题。这一点和 `std::shared_ptr` 类似[^2]
966996
967997
`std::promise` 也同,它的 `get_future()` 成员函数一样可以用来构造 `std::shared_future`,虽然它的返回类型是 `std::future`,不过不影响,这是因为 `std::shared_future` 有一个 `std::future<T>&&` 参数的[构造函数](https://zh.cppreference.com/w/cpp/thread/shared_future/shared_future),转移 `std::future` 的所有权。
968998
969999
```cpp
970-
std::promise<std::string>p;
971-
std::shared_future<std::string>sf{ p.get_future() }; // 隐式转移所有权
1000+
std::promise<std::string> p;
1001+
std::shared_future<std::string> sf{ p.get_future() }; // 隐式转移所有权
9721002
```
9731003
9741004
就不需要再强调了。
@@ -1010,7 +1040,7 @@ class duration;
10101040
如你所见,它默认的时钟节拍是 1,这是一个很重要的类,标准库通过它定义了很多的时间类型,比如 **`std::chrono::minutes`** 是分钟类型,那么它的 `Period` 就是 `std::ratio<60>` ,因为一分钟等于 60 秒。
10111041
10121042
```cpp
1013-
std::chrono::minutes std::chrono::duration</* int29 */, std::ratio<60>>
1043+
using minutes = duration<int, ratio<60>>;
10141044
```
10151045
10161046
稳定时钟(Steady Clock)是指提供稳定、持续递增的时间流逝信息的时钟。它的特点是不受系统时间调整或变化的影响,即使在系统休眠或时钟调整的情况下,它也能保持稳定。在 C++ 标准库中,[`std::chrono::steady_clock`](https://zh.cppreference.com/w/cpp/chrono/steady_clock) 就是一个稳定时钟。它通常用于测量时间间隔和性能计时等需要高精度和稳定性的场景。可以通过 `is_steady` 静态常量判断当前时钟是否是稳定时钟。

0 commit comments

Comments
 (0)