Skip to content

Commit

Permalink
更新视频代码,修改第四章措辞、格式、以及补充代码示例
Browse files Browse the repository at this point in the history
  • Loading branch information
Mq-b committed Sep 22, 2024
1 parent 8954e3c commit de1f91a
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include <iostream>
#include <thread>
#include <future> // 引入 future 头文件

void f() {
std::cout << std::this_thread::get_id() << '\n';
}

int main() {
auto t = std::async([] {});
std::future<void> future{ std::move(t) };
future.wait(); // Error! 抛出异常
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#include <iostream>
#include <thread>
#include <future>

template<typename R, typename...Ts, typename...Args>
void async_task(std::packaged_task<R(Ts...)>& task, Args&&...args) {
// todo..
task(std::forward<Args>(args)...);
}

int main() {
std::packaged_task<int(int, int)> task([](int a, int b) {
return a + b;
});

int value = 50;

std::future<int> 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<double(int, int)> task{ [](int a, int b) {
// std::cout << "packaged_task: " << std::this_thread::get_id() << '\n';
// return std::pow(a, b);
// } };
//
// std::future<double> 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();
//}
55 changes: 55 additions & 0 deletions code/ModernCpp-ConcurrentProgramming-Tutorial/29使用promise.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#include <iostream>
#include <thread>
#include <future>
#include <chrono>
using namespace std::chrono_literals;

void f(std::promise<int> obj ,int num){
// todo..
obj.set_value(num * num); // 调用了 set_value
// todo..
std::this_thread::sleep_for(5s); // 模拟一些计算
}

void throw_function(std::promise<int> 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<int> prom;
std::future<int> 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<int> 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();
//}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include <iostream>
#include <thread>
#include <future>

int main(){
std::future<void>future = 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';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#include <iostream>
#include <thread>
#include <future>

std::string fetch_data() {
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时操作
return "从网络获取的数据!";
}

int main() {
std::future<std::string> future_data = std::async(std::launch::async, fetch_data);

// // 转移共享状态,原来的 future 被清空 valid() == false
std::shared_future<std::string> 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<std::string> p;
std::shared_future<std::string> sf{ p.get_future() }; // 隐式转移所有权
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#include <chrono>
#include <iostream>
#include <iomanip>
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");
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 路径
Expand Down
50 changes: 40 additions & 10 deletions md/04同步操作.md
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,7 @@ auto sum(ForwardIt first, ForwardIt last) {
> [运行](https://godbolt.org/z/r19MYcv6e)测试。
相比于之前,其实不同无非是定义了 `std::vector<std::packaged_task<value_type()>> tasks` 与 `std::vector<std::future<value_type>> futures` ,然后在循环中制造任务插入容器,关联 tuple,再放到线程中执行。最后汇总的时候写一个循环,`futures[i].get()` 获取任务的返回值加起来即可。
相比于之前,其实不同无非是定义了 `std::vector<std::packaged_task<value_type()>> tasks` 与 `std::vector<std::future<value_type>> futures` ,然后在循环中制造任务插入容器,关联 future,再放到线程中执行。最后汇总的时候写一个循环,`futures[i].get()` 获取任务的返回值加起来即可。
到此,也就可以了。
Expand Down Expand Up @@ -799,7 +799,7 @@ int main() {
来自线程的异常: 一个异常
```
你可能对这段代码还有一些疑问:我们写的是 `promised<int>` ,但是却没有使用 `set_value` 设置值,你可能会想着再写一行 `prom.set_value(0)`
你可能对这段代码还有一些疑问:我们写的是 `promise<int>` ,但是却没有使用 `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` 对象只能是存储值或者异常其中一种,而**无法共存**
Expand Down Expand Up @@ -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<int>& 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() {
Expand Down Expand Up @@ -932,7 +962,7 @@ int main() {
}
```
这段代码存在数据竞争,就如同我们先前所说:“***在多个线程中对**同一个 **`std::shared_future` 对象进行操作时(如果没有进行同步保护)存在竞争条件***”,它并没有提供线程安全的方式。而我们的 lambda 是按引用传递,也就是“**同一个**”进行操作了。可以改为:
这段代码存在数据竞争,就如同我们先前所说:“***在多个线程中对**同一个 **`std::shared_future` 对象进行操作时(如果没有进行同步保护)存在条件竞争***”,它并没有提供线程安全的方式。而我们的 lambda 是按引用传递,也就是“**同一个**”进行操作了。可以改为:
```cpp
std::string fetch_data() {
Expand Down Expand Up @@ -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<T>&&` 参数的[构造函数](https://zh.cppreference.com/w/cpp/thread/shared_future/shared_future),转移 `std::future` 的所有权。
```cpp
std::promise<std::string>p;
std::shared_future<std::string>sf{ p.get_future() }; // 隐式转移所有权
std::promise<std::string> p;
std::shared_future<std::string> sf{ p.get_future() }; // 隐式转移所有权
```
就不需要再强调了。
Expand Down Expand Up @@ -1010,7 +1040,7 @@ class duration;
如你所见,它默认的时钟节拍是 1,这是一个很重要的类,标准库通过它定义了很多的时间类型,比如 **`std::chrono::minutes`** 是分钟类型,那么它的 `Period` 就是 `std::ratio<60>` ,因为一分钟等于 60 秒。
```cpp
std::chrono::minutes std::chrono::duration</* int29 */, std::ratio<60>>
using minutes = duration<int, ratio<60>>;
```
稳定时钟(Steady Clock)是指提供稳定、持续递增的时间流逝信息的时钟。它的特点是不受系统时间调整或变化的影响,即使在系统休眠或时钟调整的情况下,它也能保持稳定。在 C++ 标准库中,[`std::chrono::steady_clock`](https://zh.cppreference.com/w/cpp/chrono/steady_clock) 就是一个稳定时钟。它通常用于测量时间间隔和性能计时等需要高精度和稳定性的场景。可以通过 `is_steady` 静态常量判断当前时钟是否是稳定时钟。
Expand Down

0 comments on commit de1f91a

Please sign in to comment.