Skip to content

Commit 00b226d

Browse files
committed
1. 修改第四章时间点的例子 .count() 以兼容早期标准
2. 修改展示条件变量超时功能的示例 #15 3. C++20 信号量(完成本节部分内容)#12
1 parent 5808f66 commit 00b226d

File tree

1 file changed

+67
-7
lines changed

1 file changed

+67
-7
lines changed

md/04同步操作.md

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -934,7 +934,7 @@ std::this_thread::sleep_for(std::chrono::seconds(1));
934934
auto end = std::chrono::steady_clock::now();
935935
936936
auto result = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
937-
std::cout << result << '\n';
937+
std::cout << result.count() << '\n';
938938
```
939939

940940
> [运行](https://godbolt.org/z/ExdnzKoYj)测试。
@@ -952,20 +952,20 @@ std::condition_variable cv;
952952
bool done{};
953953
std::mutex m;
954954

955-
bool wait_loop(){
955+
bool wait_loop() {
956956
const auto timeout = std::chrono::steady_clock::now() + 500ms;
957957
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) {
960960
std::cout << "超时 500ms\n";
961-
break;
961+
return false;
962962
}
963963
}
964-
return done;
964+
return true;
965965
}
966966
```
967967
968-
> [运行](https://godbolt.org/z/h1T67YKb5)测试。
968+
> [运行](https://godbolt.org/z/s6vTThqK1)测试。
969969
970970
`_until` 也就是等待到一个时间点,我们设置的是等待到当前时间往后 500 毫秒。如果超过了这个时间还没有被唤醒,那就打印超时,并退出循环,函数返回 `false`。
971971
@@ -1119,6 +1119,66 @@ C++11 的 `std::this_thread::get_id()` 返回的内部类型没办法直接转
11191119
11201120
建议下载并运行此项目,通过实际操作理解代码效果。同时,可以尝试修改代码,观察不同情况下 UI 的响应情况,以加深对异步任务处理的理解。
11211121
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+
11221182
## 总结
11231183

11241184
在并发编程中,同步操作对于并发编程至关重要。如果没有同步,线程基本上就是独立的,因其任务之间的相关性,才可作为一个整体执行(比如第二章的并行求和)。本章讨论了多种用于同步操作的工具,包括条件变量、future、promise、package_task。同时,详细介绍了 C++ 时间库的知识,以使用并发支持库中的“限时等待”。最后使用 CMake + Qt 构建了一个带有 UI 界面的示例,展示异步多线程的**必要性**

0 commit comments

Comments
 (0)