Skip to content

Commit 1de7012

Browse files
committed
1. fix #18
2. 更新第三章“线程存储期”的部分措辞,增加“CPU 变量”的部分内容 #12
1 parent 31712e4 commit 1de7012

File tree

2 files changed

+15
-4
lines changed

2 files changed

+15
-4
lines changed

Diff for: md/03共享数据.md

+12-2
Original file line numberDiff line numberDiff line change
@@ -957,7 +957,7 @@ MSVC 无法使用 GCC 的编译器扩展,GCC 也肯定无法使用 MSVC 的扩
957957
958958
> ### 注意事项
959959
>
960-
> 需要注意的是,在 MSVC 的实现中,`std::async` 策略为 [`launch::async`](https://zh.cppreference.com/w/cpp/thread/launch) 并不是每次都创建一个新的线程,而是从线程池获取线程。**这意味着无法保证线程局部变量在任务完成时会被销毁**。如果线程被回收并用于新的 `std::async` 调用,则旧的线程局部变量仍然存在。因此,**建议不要将线程局部变量与 `std::async` 一起使用**。[文档](https://learn.microsoft.com/zh-cn/cpp/standard-library/future-functions?view=msvc-170)。
960+
> 需要注意的是,在 MSVC 的实现中,`std::async` 策略为 [`launch::async`](https://zh.cppreference.com/w/cpp/thread/launch) 却并不是每次都创建一个新的线程,而是从线程池获取线程。**这意味着无法保证线程局部变量在任务完成时会被销毁**。如果线程被回收并用于新的 `std::async` 调用,则旧的线程局部变量仍然存在。因此,**建议不要将线程局部变量与 `std::async` 一起使用**。[文档](https://learn.microsoft.com/zh-cn/cpp/standard-library/future-functions?view=msvc-170)。
961961
>
962962
> 虽然还没有讲 `std::async` ,不过还是可以注意一下这个问题,我们用一个简单的示例为你展示:
963963
>
@@ -989,10 +989,20 @@ MSVC 无法使用 GCC 的编译器扩展,GCC 也肯定无法使用 MSVC 的扩
989989
> 在不同编译器上的输出结果:
990990
>
991991
> - **Linux/Windows GCC、Clang**:会输出 `1`、`2`,因为线程变量 `x` 在每个任务中被正确销毁析构。
992-
> - **Windows MSVC**:会输出 `1`、`1`,因为线程被线程池复用,线程依然活跃,线程变量 x 也还未释放
992+
> - **Windows MSVC**:会输出 `1`、`1`,因为线程被线程池复用,线程依然活跃,线程变量 x 也就还未释放
993993
994994
## CPU 变量
995995
996+
CPU 变量的概念很好理解。就像线程变量为每个线程提供独立的对象实例,互不干扰一样,CPU 变量也是如此。
997+
998+
在创建 CPU 变量时,系统上的每个 CPU [^2] 都会获得该变量的一个副本。
999+
1000+
实际上,这就是在说 Linux 2.6 [^3]引入的 **Per-CPU** 内核功能。
1001+
1002+
[^2]: “*每个 CPU*”,指的是系统中的每个物理处理器或每个逻辑处理器(如果超线程被启用)。
1003+
1004+
[^3]: [Linux 内核版本历史](https://zh.wikipedia.org/wiki/Linux%E5%86%85%E6%A0%B8%E7%89%88%E6%9C%AC%E5%8E%86%E5%8F%B2)。
1005+
9961006
## 总结
9971007
9981008
本章讨论了多线程的共享数据引发的恶性条件竞争会带来的问题。并说明了可以使用互斥量(`std::mutex`)保护共享数据,并且要注意互斥量上锁的“**粒度**”。C++标准库提供了很多工具,包括管理互斥量的管理类(`std::lock_guard`),但是互斥量只能解决它能解决的问题,并且它有自己的问题(**死锁**)。同时我们讲述了一些避免死锁的方法和技术。还讲了一下互斥量所有权转移。然后讨论了面对不同情况保护共享数据的不同方式,使用 `std::call_once()` 保护共享数据的初始化过程,使用读写锁(`std::shared_mutex`)保护不常更新的数据结构。以及特殊情况可能用到的互斥量 `recursive_mutex`,有些人可能喜欢称作:**递归锁**。最后聊了一下 `new`、`delete` 运算符的库函数实际是线程安全的,以及线程存储期。

Diff for: md/05内存模型与原子操作.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -537,10 +537,11 @@ std::atomic<std::shared_ptr<int>> ptr = std::make_shared<int>(10);
537537

538538
```cpp
539539
std::atomic<std::shared_ptr<int>> ptr = std::make_shared<int>(10);
540-
ptr.store(std::make_shared<int>(100));
540+
std::atomic_ref<int> ref{ *ptr.load() };
541+
ref = 100; // 原子地赋 100 给被引用的对象
541542
```
542543
543-
通过使用 `store` 成员函数,可以原子地替换 `ptr` 所保护的值
544+
通过使用 [`std::atomic_ref`](https://zh.cppreference.com/w/cpp/atomic/atomic_ref) 我们得以确保在修改共享资源时保持操作的原子性,从而避免了数据竞争
544545
545546
---
546547

0 commit comments

Comments
 (0)