@@ -934,7 +934,7 @@ std::this_thread::sleep_for(std::chrono::seconds(1));
934
934
auto end = std::chrono::steady_clock::now();
935
935
936
936
auto result = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
937
- std::cout << result << '\n';
937
+ std::cout << result.count() << '\n';
938
938
```
939
939
940
940
> [ 运行] ( https://godbolt.org/z/ExdnzKoYj ) 测试。
@@ -952,20 +952,20 @@ std::condition_variable cv;
952
952
bool done{};
953
953
std::mutex m;
954
954
955
- bool wait_loop(){
955
+ bool wait_loop() {
956
956
const auto timeout = std::chrono::steady_clock::now() + 500ms;
957
957
std::unique_lock< std::mutex > lk{ m };
958
- while(!done){
959
- if(cv.wait_until(lk,timeout) == std::cv_status::timeout){
958
+ while (!done) {
959
+ if (cv.wait_until(lk, timeout) == std::cv_status::timeout) {
960
960
std::cout << "超时 500ms\n";
961
- break ;
961
+ return false ;
962
962
}
963
963
}
964
- return done ;
964
+ return true ;
965
965
}
966
966
```
967
967
968
- > [运行](https://godbolt.org/z/h1T67YKb5 )测试。
968
+ > [运行](https://godbolt.org/z/s6vTThqK1 )测试。
969
969
970
970
`_until` 也就是等待到一个时间点,我们设置的是等待到当前时间往后 500 毫秒。如果超过了这个时间还没有被唤醒,那就打印超时,并退出循环,函数返回 `false`。
971
971
@@ -1119,6 +1119,66 @@ C++11 的 `std::this_thread::get_id()` 返回的内部类型没办法直接转
1119
1119
1120
1120
建议下载并运行此项目,通过实际操作理解代码效果。同时,可以尝试修改代码,观察不同情况下 UI 的响应情况,以加深对异步任务处理的理解。
1121
1121
1122
+ ## C++20 信号量
1123
+
1124
+ C++20 引入了**信号量**,对于那些熟悉操作系统或其它并发支持库的开发者来说,这个同步设施的概念应该不会感到陌生。[信号量](https://zh.wikipedia.org/wiki/%E4%BF%A1%E5%8F%B7%E9%87%8F)源自操作系统,是一个古老而广泛应用的概念,在各种编程语言中都有自己的抽象实现。然而,C++ 标准库对其的支持却来得很晚,在 C++20 中才得以引入。
1125
+
1126
+ 信号量是一个非常**轻量简单**的同步设施,它维护一个计数,这个计数不能小于 `0`。信号量提供两种基本操作:**释放**(增加计数)和**等待**(减少计数)。如果当前信号量的计数值为 `0`,那么执行“***等待***”操作的线程将会**一直阻塞**,直到计数大于 `0`,也就是其它线程执行了“***释放***”操作。
1127
+
1128
+ C++ 提供了两个信号量类型:`std::counting_semaphore` 与 `std::binary_semaphore`,定义在 [`<semaphore>`](https://zh.cppreference.com/w/cpp/header/semaphore) 中。
1129
+
1130
+ `binary_semaphore`[^4] 只是 `counting_semaphore` 的别名:
1131
+
1132
+ ```cpp
1133
+ using binary_semaphore = counting_semaphore<1>;
1134
+ ```
1135
+
1136
+ 好了,我们举一个简单的例子来使用一下:
1137
+
1138
+ ``` cpp
1139
+ // 全局二元信号量对象
1140
+ // 设置对象初始计数为 0
1141
+ std::binary_semaphore smph_signal_main_to_thread{ 0 };
1142
+ std::binary_semaphore smph_signal_thread_to_main{ 0 };
1143
+
1144
+ void thread_proc() {
1145
+ smph_signal_main_to_thread.acquire();
1146
+ std::cout << "[ 线程] 获得信号" << std::endl;
1147
+
1148
+ std::this_thread::sleep_for (3s);
1149
+
1150
+ std::cout << "[线程] 发送信号\n";
1151
+ smph_signal_thread_to_main.release();
1152
+ }
1153
+
1154
+ int main () {
1155
+ std::jthread thr_worker{ thread_proc };
1156
+
1157
+ std::cout << "[主] 发送信号\n";
1158
+ smph_signal_main_to_thread.release();
1159
+
1160
+ smph_signal_thread_to_main.acquire();
1161
+ std::cout << "[主] 获得信号\n";
1162
+ }
1163
+ ```
1164
+
1165
+ [ ** 运行结果** ] ( https://godbolt.org/z/c55MeGYrT ) :
1166
+
1167
+ ``` txt
1168
+ [主] 发送信号
1169
+ [线程] 获得信号
1170
+ [线程] 发送信号
1171
+ [主] 获得信号
1172
+ ```
1173
+
1174
+ [ ` acquire ` ] ( https://zh.cppreference.com/w/cpp/thread/counting_semaphore/acquire ) 函数就是我们先前说的“* 等待* ”(减少计数),` release ` 函数就是"释放"(增加计数)。
1175
+
1176
+ ---
1177
+
1178
+ 信号量常用于发信/提醒而非互斥,通过初始化该信号量为 0 从而阻塞尝试 acquire() 的接收者,直至提醒者通过调用 release(n) “发信”。在此方面可把信号量当作** 条件变量的替代品** ,** 通常它有更好的性能** 。
1179
+
1180
+ [ ^ 4 ] :注:** 如果信号量只有二进制的 0 或 1,称为二进制信号量(binary semaphore)** ,这就是这个类型名字的由来。
1181
+
1122
1182
## 总结
1123
1183
1124
1184
在并发编程中,同步操作对于并发编程至关重要。如果没有同步,线程基本上就是独立的,因其任务之间的相关性,才可作为一个整体执行(比如第二章的并行求和)。本章讨论了多种用于同步操作的工具,包括条件变量、future、promise、package_task。同时,详细介绍了 C++ 时间库的知识,以使用并发支持库中的“限时等待”。最后使用 CMake + Qt 构建了一个带有 UI 界面的示例,展示异步多线程的** 必要性** 。
0 commit comments