Skip to content

Commit 8b2dadb

Browse files
committed
修改第五章 atomic<shared_ptr> 一节的内容,主要是增加对 std::shared_ptr 本身的概念进行解释
1 parent e74c3e1 commit 8b2dadb

File tree

1 file changed

+26
-1
lines changed

1 file changed

+26
-1
lines changed

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,7 @@ p.fetch_add(3, std::memory_order_release);
464464
465465
在前文中,我们多次提到 `std::shared_ptr`:
466466
467-
> 第四章中提到:*多个线程能在不同的 shared_ptr 对象上调用**所有成员函数**(包含复制构造函数与复制赋值)而不附加同步,即使这些实例是同一对象的副本且共享所有权也是如此。若多个执行线程访问**同一 shared_ptr** 对象而不同步,且任一线程使用 shared_ptr 的非 const 成员函数,则将出现数据竞争;`std::atomic<shared_ptr>` 能用于避免数据竞争。[文档](https://zh.cppreference.com/w/cpp/memory/shared_ptr#:~:text=%E5%A4%9A%E4%B8%AA%E7%BA%BF%E7%A8%8B%E8%83%BD%E5%9C%A8%E4%B8%8D%E5%90%8C%E7%9A%84%20shared_ptr%20%E5%AF%B9%E8%B1%A1%E4%B8%8A%E8%B0%83%E7%94%A8%E6%89%80%E6%9C%89%E6%88%90%E5%91%98%E5%87%BD%E6%95%B0%EF%BC%88%E5%8C%85%E5%90%AB%E5%A4%8D%E5%88%B6%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0%E4%B8%8E%E5%A4%8D%E5%88%B6%E8%B5%8B%E5%80%BC%EF%BC%89%E8%80%8C%E4%B8%8D%E9%99%84%E5%8A%A0%E5%90%8C%E6%AD%A5%EF%BC%8C%E5%8D%B3%E4%BD%BF%E8%BF%99%E4%BA%9B%E5%AE%9E%E4%BE%8B%E6%98%AF%E5%90%8C%E4%B8%80%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%89%AF%E6%9C%AC%E4%B8%94%E5%85%B1%E4%BA%AB%E6%89%80%E6%9C%89%E6%9D%83%E4%B9%9F%E6%98%AF%E5%A6%82%E6%AD%A4%E3%80%82%E8%8B%A5%E5%A4%9A%E4%B8%AA%E6%89%A7%E8%A1%8C%E7%BA%BF%E7%A8%8B%E8%AE%BF%E9%97%AE%E5%90%8C%E4%B8%80%20shared_ptr%20%E5%AF%B9%E8%B1%A1%E8%80%8C%E4%B8%8D%E5%90%8C%E6%AD%A5%EF%BC%8C%E4%B8%94%E4%BB%BB%E4%B8%80%E7%BA%BF%E7%A8%8B%E4%BD%BF%E7%94%A8%20shared_ptr%20%E7%9A%84%E9%9D%9E%20const%20%E6%88%90%E5%91%98%E5%87%BD%E6%95%B0%EF%BC%8C%E5%88%99%E5%B0%86%E5%87%BA%E7%8E%B0%E6%95%B0%E6%8D%AE%E7%AB%9E%E4%BA%89%EF%BC%9Bstd%3A%3Aatomic%3Cshared_ptr%3E%20%E8%83%BD%E7%94%A8%E4%BA%8E%E9%81%BF%E5%85%8D%E6%95%B0%E6%8D%AE%E7%AB%9E%E4%BA%89%E3%80%82)。*
467+
> 第四章中提到:*多个线程能在不同的 shared_ptr 对象上调用**所有成员函数**[^3](包含复制构造函数与复制赋值)而不附加同步,即使这些实例是同一对象的副本且共享所有权也是如此。若多个执行线程访问**同一 shared_ptr** 对象而不同步,且任一线程使用 shared_ptr 的非 const 成员函数,则将出现数据竞争;`std::atomic<shared_ptr>` 能用于避免数据竞争。[文档](https://zh.cppreference.com/w/cpp/memory/shared_ptr#:~:text=%E5%A4%9A%E4%B8%AA%E7%BA%BF%E7%A8%8B%E8%83%BD%E5%9C%A8%E4%B8%8D%E5%90%8C%E7%9A%84%20shared_ptr%20%E5%AF%B9%E8%B1%A1%E4%B8%8A%E8%B0%83%E7%94%A8%E6%89%80%E6%9C%89%E6%88%90%E5%91%98%E5%87%BD%E6%95%B0%EF%BC%88%E5%8C%85%E5%90%AB%E5%A4%8D%E5%88%B6%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0%E4%B8%8E%E5%A4%8D%E5%88%B6%E8%B5%8B%E5%80%BC%EF%BC%89%E8%80%8C%E4%B8%8D%E9%99%84%E5%8A%A0%E5%90%8C%E6%AD%A5%EF%BC%8C%E5%8D%B3%E4%BD%BF%E8%BF%99%E4%BA%9B%E5%AE%9E%E4%BE%8B%E6%98%AF%E5%90%8C%E4%B8%80%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%89%AF%E6%9C%AC%E4%B8%94%E5%85%B1%E4%BA%AB%E6%89%80%E6%9C%89%E6%9D%83%E4%B9%9F%E6%98%AF%E5%A6%82%E6%AD%A4%E3%80%82%E8%8B%A5%E5%A4%9A%E4%B8%AA%E6%89%A7%E8%A1%8C%E7%BA%BF%E7%A8%8B%E8%AE%BF%E9%97%AE%E5%90%8C%E4%B8%80%20shared_ptr%20%E5%AF%B9%E8%B1%A1%E8%80%8C%E4%B8%8D%E5%90%8C%E6%AD%A5%EF%BC%8C%E4%B8%94%E4%BB%BB%E4%B8%80%E7%BA%BF%E7%A8%8B%E4%BD%BF%E7%94%A8%20shared_ptr%20%E7%9A%84%E9%9D%9E%20const%20%E6%88%90%E5%91%98%E5%87%BD%E6%95%B0%EF%BC%8C%E5%88%99%E5%B0%86%E5%87%BA%E7%8E%B0%E6%95%B0%E6%8D%AE%E7%AB%9E%E4%BA%89%EF%BC%9Bstd%3A%3Aatomic%3Cshared_ptr%3E%20%E8%83%BD%E7%94%A8%E4%BA%8E%E9%81%BF%E5%85%8D%E6%95%B0%E6%8D%AE%E7%AB%9E%E4%BA%89%E3%80%82)。*
468468
469469
一个在互联网上非常热门的八股问题是:***`std::shared_ptr` 是不是线程安全?***
470470
@@ -521,6 +521,29 @@ int main(){
521521

522522
2. 任一线程使用 shared_ptr 的**非 const** 成员函数
523523

524+
那么**为什么呢?**为什么满足这些概念就是线程不安全呢?为了理解这些概念,首先需要了解 shared_ptr 的内部实现:
525+
526+
shared_ptr 的通常实现只保有两个指针
527+
528+
- 指向底层元素的指针([get()](https://zh.cppreference.com/w/cpp/memory/shared_ptr/get)) 所返回的指针)
529+
- 指向*控制块* 的指针
530+
531+
**控制块**是一个动态分配的对象,其中包含:
532+
533+
- 指向被管理对象的指针或被管理对象本身
534+
- 删除器(类型擦除)
535+
- 分配器(类型擦除)
536+
- 持有被管理对象的 `shared_ptr` 的数量
537+
- 涉及被管理对象的 `weak_ptr` 的数量
538+
539+
**控制块是线程安全的**,这意味着多个线程可以安全地操作引用计数和访问管理对象,即使这些 `shared_ptr` 实例是同一对象的副本且共享所有权也是如此。因此,多个线程可以安全地创建、销毁和复制 `shared_ptr` 对象,因为这些操作仅影响控制块中的引用计数。
540+
541+
然而,`shared_ptr` 对象实例本身并不是线程安全的。`shared_ptr` 对象实例包含一个指向控制块的指针和一个指向底层元素的指针。这两个指针的操作在多个线程中并没有同步机制。因此,如果多个线程同时访问同一个 `shared_ptr` 对象实例并调用非 `const` 成员函数(如 `reset` 或 `operator=`),这些操作会导致对这些指针的并发修改,进而引发数据竞争。
542+
543+
如果不是同一 shared_ptr 对象,每个线程读写的指针也不是同一个,控制块又是线程安全的,那么自然不存在数据竞争,可以安全的调用所有成员函数。
544+
545+
---
546+
524547
使用 `std::atomic<shared_ptr>` 修改:
525548

526549
```cpp
@@ -557,6 +580,8 @@ void reader() {
557580

558581
不过事实上 `std::atomic<std::shared_ptr>` 的功能相当有限,单看它提供的修改接口(`=``store``load``exchang`)就能明白。如果要操作其保护的共享指针指向的资源还是得 `load()` 获取底层共享指针的副本。此时再进行操作时就得考虑 `std::shared_ptr` 本身在多线程的支持了。
559582

583+
[^3]: 不用感到奇怪,之所以多个线程通过 shared_ptr 的副本可以调用一切成员函数,甚至包括非 const 的成员函数 `operator=``reset`,是因为 ``shared_ptr` 的**控制块是线程安全的**
584+
560585
---
561586

562587
在使用 `std::atomic<std::shared_ptr>` 的时候,我们要注意第三章中关于共享数据的一句话:

0 commit comments

Comments
 (0)