Skip to content

Commit

Permalink
1. 修改第四章时间点的例子 .count() 以兼容早期标准
Browse files Browse the repository at this point in the history
2. 修改展示条件变量超时功能的示例 #15
3. C++20 信号量(完成本节部分内容)#12
  • Loading branch information
Mq-b committed May 30, 2024
1 parent 5808f66 commit 00b226d
Showing 1 changed file with 67 additions and 7 deletions.
74 changes: 67 additions & 7 deletions md/04同步操作.md
Original file line number Diff line number Diff line change
Expand Up @@ -934,7 +934,7 @@ std::this_thread::sleep_for(std::chrono::seconds(1));
auto end = std::chrono::steady_clock::now();
auto result = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << result << '\n';
std::cout << result.count() << '\n';
```

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

bool wait_loop(){
bool wait_loop() {
const auto timeout = std::chrono::steady_clock::now() + 500ms;
std::unique_lock<std::mutex> lk{ m };
while(!done){
if(cv.wait_until(lk,timeout) == std::cv_status::timeout){
while (!done) {
if (cv.wait_until(lk, timeout) == std::cv_status::timeout) {
std::cout << "超时 500ms\n";
break;
return false;
}
}
return done;
return true;
}
```
> [运行](https://godbolt.org/z/h1T67YKb5)测试。
> [运行](https://godbolt.org/z/s6vTThqK1)测试。
`_until` 也就是等待到一个时间点,我们设置的是等待到当前时间往后 500 毫秒。如果超过了这个时间还没有被唤醒,那就打印超时,并退出循环,函数返回 `false`。
Expand Down Expand Up @@ -1119,6 +1119,66 @@ C++11 的 `std::this_thread::get_id()` 返回的内部类型没办法直接转
建议下载并运行此项目,通过实际操作理解代码效果。同时,可以尝试修改代码,观察不同情况下 UI 的响应情况,以加深对异步任务处理的理解。
## C++20 信号量
C++20 引入了**信号量**,对于那些熟悉操作系统或其它并发支持库的开发者来说,这个同步设施的概念应该不会感到陌生。[信号量](https://zh.wikipedia.org/wiki/%E4%BF%A1%E5%8F%B7%E9%87%8F)源自操作系统,是一个古老而广泛应用的概念,在各种编程语言中都有自己的抽象实现。然而,C++ 标准库对其的支持却来得很晚,在 C++20 中才得以引入。
信号量是一个非常**轻量简单**的同步设施,它维护一个计数,这个计数不能小于 `0`。信号量提供两种基本操作:**释放**(增加计数)和**等待**(减少计数)。如果当前信号量的计数值为 `0`,那么执行“***等待***”操作的线程将会**一直阻塞**,直到计数大于 `0`,也就是其它线程执行了“***释放***”操作。
C++ 提供了两个信号量类型:`std::counting_semaphore` 与 `std::binary_semaphore`,定义在 [`<semaphore>`](https://zh.cppreference.com/w/cpp/header/semaphore) 中。
`binary_semaphore`[^4] 只是 `counting_semaphore` 的别名:
```cpp
using binary_semaphore = counting_semaphore<1>;
```

好了,我们举一个简单的例子来使用一下:

```cpp
// 全局二元信号量对象
// 设置对象初始计数为 0
std::binary_semaphore smph_signal_main_to_thread{ 0 };
std::binary_semaphore smph_signal_thread_to_main{ 0 };

void thread_proc() {
smph_signal_main_to_thread.acquire();
std::cout << "[线程] 获得信号" << std::endl;

std::this_thread::sleep_for(3s);

std::cout << "[线程] 发送信号\n";
smph_signal_thread_to_main.release();
}

int main() {
std::jthread thr_worker{ thread_proc };

std::cout << "[主] 发送信号\n";
smph_signal_main_to_thread.release();

smph_signal_thread_to_main.acquire();
std::cout << "[主] 获得信号\n";
}
```

[**运行结果**](https://godbolt.org/z/c55MeGYrT)

```txt
[主] 发送信号
[线程] 获得信号
[线程] 发送信号
[主] 获得信号
```

[`acquire`](https://zh.cppreference.com/w/cpp/thread/counting_semaphore/acquire) 函数就是我们先前说的“*等待*”(减少计数),`release` 函数就是"释放"(增加计数)。

---

信号量常用于发信/提醒而非互斥,通过初始化该信号量为 0 从而阻塞尝试 acquire() 的接收者,直至提醒者通过调用 release(n) “发信”。在此方面可把信号量当作**条件变量的替代品****通常它有更好的性能**

[^4]:注:**如果信号量只有二进制的 0 或 1,称为二进制信号量(binary semaphore)**,这就是这个类型名字的由来。

## 总结

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

0 comments on commit 00b226d

Please sign in to comment.