@@ -221,6 +221,8 @@ struct trivial_type {
221
221
3. **Read-modify-write(读-改-写)操作**:可选的内存序包括 `memory_order_relaxed`、`memory_order_consume`、`memory_order_acquire`、`memory_order_release`、`memory_order_acq_rel`、`memory_order_seq_cst`。
222
222
223
223
> 本节主要广泛介绍 `std::atomic`,而未展开具体使用。在后续章节中,我们将更详细地讨论一些版本,如 `std::atomic<bool>`,并介绍其成员函数和使用方法。
224
+ >
225
+ > 最后强调一下:任何 [std::atomic](https://zh.cppreference.com/w/cpp/atomic/atomic) 类型,**初始化不是原子操作**。
224
226
225
227
### `st::atomic_flag`
226
228
@@ -419,13 +421,96 @@ else {
419
421
420
422
在前文中,我们多次提到 ` std::shared_ptr ` :
421
423
422
- > 第四章中提到:*多个线程能在不同的 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)。*
424
+ > 第四章中提到:*多个线程能在不同的 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)。*
423
425
424
426
一个在互联网上非常热门的八股问题是:*** ` std::shared_ptr ` 是不是线程安全?***
425
427
426
- 显然,它并不是完全线程安全的,尽管在多线程环境中有一定保证,但这还不够。在 C++20 中,原子模板 ` std::atomic ` 引入了一个偏特化版本 [ ` std::atomic<std::shared_ptr> ` ] ( https://zh.cppreference.com/w/cpp/memory/shared_ptr/atomic2 ) 允许用户原子地操纵 ` shared_ptr ` 对象。因为它是 ` std::atomic ` 的特化版本,即使我们还没有深入讲述它,也能知道它是** 原子类型** ,这意味着它的所有操作都是** 原子操作** 。
428
+ 显然,它并不是完全线程安全的,尽管在多线程环境中有很大的保证,但这还不够。在 C++20 中,原子模板 ` std::atomic ` 引入了一个偏特化版本 [ ` std::atomic<std::shared_ptr> ` ] ( https://zh.cppreference.com/w/cpp/memory/shared_ptr/atomic2 ) 允许用户原子地操纵 ` shared_ptr ` 对象。因为它是 ` std::atomic ` 的特化版本,即使我们还没有深入讲述它,也能知道它是** 原子类型** ,这意味着它的所有操作都是** 原子操作** 。
429
+
430
+ 若多个执行线程不同步地同时访问** 同一** ` std::shared_ptr ` 对象,且任何这些访问使用了 shared_ptr 的** 非 const 成员函数** ,则将** 出现数据竞争** ,** 除非通过 ` std::atomic<std::shared_ptr> ` 的实例进行所有访问** 。
431
+
432
+ ``` cpp
433
+ class Data {
434
+ public:
435
+ Data(int value = 0) : value_ (value) {}
436
+ int get_value() const { return value_ ; }
437
+ void set_value(int new_value) { value_ = new_value; }
438
+ private:
439
+ int value_ ;
440
+ };
441
+
442
+ auto data = std::make_shared<Data >();
443
+
444
+ void writer(){
445
+ for (int i = 0; i < 10; ++i) {
446
+ std::shared_ptr<Data > new_data = std::make_shared<Data >(i);
447
+ data.swap(new_data); // 调用非 const 成员函数
448
+ std::this_thread::sleep_for(100ms);
449
+ }
450
+ }
451
+
452
+ void reader(){
453
+ for (int i = 0; i < 10; ++i) {
454
+ if (data) {
455
+ std::cout << "读取线程值: " << data->get_value() << std::endl;
456
+ }
457
+ else {
458
+ std::cout << "没有读取到数据" << std::endl;
459
+ }
460
+ std::this_thread::sleep_for(100ms);
461
+ }
462
+ }
463
+
464
+ int main(){
465
+ std::thread writer_thread{ writer };
466
+ std::thread reader_thread{ reader };
467
+
468
+ writer_thread.join();
469
+ reader_thread.join();
470
+ }
471
+ ```
472
+
473
+ > [运行](https://godbolt.org/z/6zo7hK8h1)测试。
474
+
475
+ 以上这段代码是典型的**线程不安全**,它满足:
476
+
477
+ - [x] 多个线程不同步地同时访问**同一** `std::shared_ptr` 对象
478
+
479
+ - [x] 任一线程使用 shared_ptr 的**非 const** 成员函数
480
+
481
+ ---
482
+
483
+ 不过事实上 `std::atomic<std::shared_ptr>` 的功能相当有限,单看它提供的修改接口(`=`、`store`、`load`、`exchang`)就能明白。如果要操作其保护的共享指针指向的资源还是得 `load()` 获取底层共享指针的副本。
484
+
485
+
486
+
487
+ ---
488
+
489
+ 在使用 `std::atomic<std::shared_ptr>` 的时候,我们要注意第三章中关于共享数据的一句话:
490
+
491
+ > **切勿将受保护数据的指针或引用传递到互斥量作用域之外**,不然保护将**形同虚设**。
492
+
493
+ 原子类型也有类似的问题,以下是一个例子:
494
+
495
+ ```cpp
496
+ std::atomic<std::shared_ptr<int>> ptr = std::make_shared<int>(10);
497
+ *ptr.load() = 100;
498
+ ```
499
+
500
+ 1 . 调用 ` load() ` 成员函数,原子地返回底层共享指针的** 副本** ` std::shared_ptr `
501
+ 2 . 解引用,等价 ` *get() ` ,返回了 ` int& `
502
+ 3 . 直接修改这个引用所指向的资源。
503
+
504
+ 在第一步时,已经脱离了 ` std::atomic ` 的保护,第二步就获取了被保护的数据的引用,第三步进行了修改,这导致了数据竞争。当然了,这是做法非常的愚蠢,只是为了表示,所谓的线程安全,也是要靠** 开发者的正确使用** 。
505
+
506
+ 正确的用法如下:
507
+
508
+ ``` cpp
509
+ std::atomic<std::shared_ptr<int >> ptr = std::make_shared<int >(10 );
510
+ ptr.store(std::make_shared<int >(100 ));
511
+ ```
427
512
428
- 若多个执行线程不同步地同时访问同一 ` std::shared_ptr ` 对象,且任何这些访问使用了 shared_ptr 的非 const 成员函数,则将 ** 出现数据竞争 ** , ** 除非通过 ` std::atomic<std::shared_ptr> ` 的实例进行所有访问 ** 。
513
+ 通过使用 ` store ` 成员函数,可以原子地替换 ` ptr ` 所保护的值 。
429
514
430
515
## 内存次序
431
516
0 commit comments