@[TOC](从C++20 shared_ptr移除unique()方法浅析多线程同步)

std::shared_ptrunique()方法做了什么事情?

unique()作为std::shared_ptr的成员函数,它检查当前shared_ptr持有的对象,是不是该对象的唯一持有者。也就是说检查shard_ptr的引用计数是否为1。大概的实现如下

bool unique() {return this->use_count() == 1;
}

工程上我曾用它来管控对象的所有权。如:多个对象同时通过std::shared_ptr持有a_object,但是我希望b_object是最后一个持有a_object的对象,也就是说希望a_objectb_object释放,于是,在b_object中设计如下函数:

class a_object;
class b_object {
void check_unique() {if (a_holder_.unique()) {a_holder_.reset();} else {// 一段时间之后再执行check_unique()...}
}
shared_ptr<a_object> a_holder_;
};

shared_ptr()引用计数通过什么保证线程安全?

根据gcc 10.02源码,shared_ptr引用计数的类型是_Atomic_word原子变量,增加引用计数使用memory_order_acq_relcas,而返回引用计数的函数use_count()使用memory_order_relaxed atomic load。下面分别分析:

  1. cas(compare and swap)使用memory_order_acq_rel,根据memory_order_acq_rel的语义,可以保证cas操作之前的内存读写都不能重排到该操作之后,它之后的内存读写都不能重排到它之前. 其他线程中所有对该原子变量的release operation及其之前的写入都对当前线程该cas操作后可见,并且截止到该cas操作的所有内存写入都对另外线程对它的acquire operation以及之后的内存操作可见。
  2. use_count()使用memory_order_relaxed,它只有宽松的内存序,仅保证自身的原子性,不会对任何其他变量的读写产生影响。也不会对其他线程内存同步。

通过以上的分析,我们知道了:

  1. shared_ptr增加引用计数的操作,也就是在shared_ptr获取对象的过程中,其可以和其他线程形成happens-before关系,从而建立某些变量间的memory order。由于我们一般使用shared_ptr<T> sp(new T)shared_ptr<T> sp(make_shared<T>)方式创建动态指针,因此如果两个shared_ptr<T>建立了happens-before关系,可以保证shared_ptr持有的对象对两个shared_ptr<T>对象都可见
  2. use_count()使用memory_order_relaxed宽松的内存序,仅保证自身的原子性,也就是说虽然use_count()访问的是原子变量,但是其并不保证memory order,并且use_count()的返回值只能大概反映出当前对象的引用计数,至于为什么,我们在下一节分析。

为什么C++20移除了unique()方法?

std::shared_ptrunique()方法在C++17中被废弃,在C++20中被移除。在cpp reference中有如下说明:

This function was deprecated in C++17 and removed in C++20 because use_count is only an approximation in multithreaded environment (see Notes in use_count)

可以理解,unique()本质上通过调用use_count()判断引用计数是否为1,而use_count()并不能提供unique()所代表的语义:当前shared_ptr是否为持有对象的唯一持有者。cppreference中std::shared_ptr<T>::use_count()的解释的更为详细:

comparison with ​0​. If use_count returns zero, the shared pointer is empty and manages no objects (whether or not its stored pointer is null). In multithreaded environment, this does not imply that the destructor of the managed object has completed.

comparison with 1. If use_count returns 1, there are no other owners. (The deprecated member function unique() is provided for this use case.) In multithreaded environment, this does not imply that the object is safe to modify because accesses to the managed object by former shared owners may not have completed, and because new shared owners may be introduced concurrently, such as by std::weak_ptr::lock.

也就是说,由于use_count()大多数的实现方法(比如我使用的gcc)都使用memory_order_relaxed,导致在多线程场景下,会出现如下状况:

  1. use_count() = 0并不能说明其他线程对shared_ptr持有对象的释放已经完成了,相反,由于使用memory_order_relaxed,有可能其他线程正在释放对象的过程中,只不过调用use_count()的线程没有看到
  2. use_count() = 1并不能说明当前线程是唯一一个持有该对象的线程,只能说当前线程看到shared_ptr的引用计数值为1,其他线程可能处在释放智能指针的过程中,也有可能正准备获取该智能指针。前者之所以会出现是因为memory_order_relaxed不保证跨线程memory order,而后者是无法通过任何手段避免的,除非加锁。

结论

std::shared_ptrunique()方法在C++20中被移除,主要原因是因为其实现方法并不能实现所代表的语义。移除之后,我们仍可以通过在代码中加入形如use_count() == 1的逻辑去判断当前线程是唯一持有对象的线程,前提是我们知晓了它背后的缺陷,并根据实际的应用场景加合适的memory fensemutex

一个unique()函数,其实可以引伸出无数多线程编程理论和思想,这些思想可能在我们日常业务编码中用不到,可却随着现代编程方法的演进,实打实的影响着每一位软件开发工程师。笔者作为一个小菜鸟,在此也只是阐述自己的理解,如果不对之处欢迎指教!

参考链接

  1. C++标准库(五)之智能指针源码剖析
  2. C++ memory order循序渐进(一)—— 多核编程中的lock free和memory model

从C++20 shared_ptr移除unique()方法浅析多线程同步相关推荐

  1. C++实现多线程及其三种方法实现多线程同步

    1.调用windows API实现多线程 #include "stdafx.h" #include <windows.h> #include <stdio.h&g ...

  2. jQuery unbind 删除绑定事件 / 移除标签方法

    jQuery unbind 删除绑定事件 unbind([type],[data]) 是 bind()的反向操作,从每一个匹配的元素中删除绑定的事件.如果没有参数,则删除所有绑定的事件.你可以将你用b ...

  3. 添加元素的注意问题 复习 介绍 元素的创建 元素添加的方法 元素移除的方法

    添加元素的注意问题 <!DOCTYPE html> <html lang="en"> <head><meta charset=" ...

  4. jQuery之移除元素方法

    jQuery之移除元素方法 一.empty()方法 从DOM中移除集合中匹配元素的所有子节点. 示例: <div class="container"><div c ...

  5. JLINK 10针J和20针JTAG接口连接方法

    我的JLINK终于用上了,哈哈,好开心,终于不用考虑是不是要借用别人的PC机了,昨天到城隍庙电子市场忙活了一下午,终于算是满载而归,呵呵,好了,下面说一下接法,其实根本不需要什么转接板什么的,直接把相 ...

  6. Delphi关于多线程同步的一些方法

    (注:本文为转载  http://hi.baidu.com/navy1130/blog/item/468fcdc448794fce38db49ee.html) 线程是进程内一个相对独立的.可调度的执行 ...

  7. c语言实验题——字符串排序,C语言中实现“三个数由小到大排序”的多种方法浅析...

    本文通过一个简单示例"三个数由小到大排序",将C语言中许多知识点融会贯通起来,这多种方法的实现可以将函数.宏.指针之间的区别和本质清晰的展示给读者,使本来很复杂难以理解的概念变得通 ...

  8. java多线程同步5种方法

    一.引言 前几天面试,被大师虐残了,好多基础知识必须得重新拿起来啊.闲话不多说,进入正题. 二.为什么要线程同步 因为当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会 ...

  9. 阿里面试题,为什么wait()方法要放在同步块中?

    某天我在***的时候,突然有个小伙伴微信上说:"哥,阿里面试又又挂了,被问到为什么wait()方法要放在同步块中,没答出来!" 我顿时觉得**一紧,仔细回顾一下,如果wait()方 ...

最新文章

  1. 2020年,数据中心的绿色技术演进与创新
  2. boost::hana::detail::type_foldr1用法的测试程序
  3. 信息学奥赛一本通(C++)在线评测系统——基础(一)C++语言—— 1039:判断数正负
  4. 安防专用交换机的应用介绍
  5. 李学勤:功利化是现在教育的最大问题
  6. 通盘无妙手,恒生电子落子 Light 云,三大新品持续提升金融科技生产力
  7. java基础 通过继承Thread类和实现Runnable接口创建线程
  8. VS设置程序启动权限为管理员权限
  9. java 代码里设置环境变量_如何在一个java程序里设置环境变量
  10. Nagios_快速配置
  11. 你的微信还安全吗?揭露清理僵尸粉的连环骗局
  12. 微信小程序图片自适应屏幕大小真的有效
  13. html表单中文字前黑点怎么弄,如何将word文档中标题前的黑点去掉
  14. mysql 创建 utf-8 数据库_mysql 创建数据库 utf-8
  15. PDF如何免费转Word
  16. 持续维护中|最全数据分析资料汇总(趣味Python、商业数据分析、爬虫、高效工具等等)
  17. QuTrunk与Paddle结合实践--VQA算法示例
  18. 消息队列消息丢失和消息重复发送的处理策略
  19. SparkStreaming 实现广告计费系统中在线黑名单过滤实战
  20. js中replace函数的使用

热门文章

  1. 11.向量vector.rs
  2. 简单Hook SYSENTER
  3. 网狐动态数组CWHArray
  4. 交换机网络嗅探方法之用ARP欺骗辅助嗅探
  5. 对弈程序基本技术---最小-最大搜索
  6. Flink 架构:三层架构体系、运行时组件
  7. Python的列表List常见操作
  8. Java集合框架:ArrayList
  9. STL中算法锦集(二)
  10. LiveVideoStackCon 北京站,好久不见