用户态无锁且线程安全的强弱指针C++实现

引言

最近闲来,将我几年前写的一个无锁且线程安全的强弱指针C++实现分享给大伙。

1 引出问题

C++没有像Java那样的垃圾自动回收机制,需要我们自己管理对象内存。当访问一个对象结束的时候,也不知道是否还有其他需要使用该对象之处,也不知道将来是否会被其他模块使用。

2 方案分析

2.1 快速解决问题

为了解决上述问题,通过在一个对象之上添加引用计数器,当引用开始加一、引用结束减一。在引用计数变为0的时候,就释放对象。其设计逻辑如下图:
在这里插入图片描述
根据上图,定义一个基类RefObject,其中有一个引用计数refCount, 通过方法addRef和unRef对该值加一、减一操作,两个方法都是返回修改后的refCount值;定义一个模板类SharePtr,该模板类持有RefObject的指针,当接收RefObject赋值给指针时,对RefObject执行addRef,当对象释放或RefObject指针被重新赋值后,之前的非空RefObject就执行unRef,unRef方法返回0则释放该指针指向的内存。
针对多线程环境下,为了线程安全需要对refCount加锁是一种解决方式,但是对于频繁申请释放对象的时候,加锁和释放都会陷入内核态的过程,影响性能。通过无锁化的原子操作便可以解决这个问题。

2.2 美中不足

前面的方案能解决单向持有的问题,但不能解决双向持有。具体如下:
在这里插入图片描述
如上图,Parent持有Child共享指针,其Child也持有Parent的共享指针。当所以引用Parent的共享指针释放后,Parent的引用计数还被Child持有,不能降为0,导致无法释放Parent;对Child也是同理。

2.3 完美解决

为了解决这个问题,我们设计出一种强弱智能指针。强指针可以使用对象,控制的引用计数变为零的时候会释放对象。弱指针不能使用对象,控制的引用计数变为零的时候不会释放对象,弱指针要使用指针必须将其转换为强指针。针对上面的问题,我们让parent持有child的强引用指针,让child持有parent的弱引用指针。

3 方案设计

3.1 总体逻辑图

在这里插入图片描述
根据上图,RefObject是被持有对象的基类,属性refWeak是弱引用的包装器的指针,属性refCount是强引用计数;StrongPtr是对强引用指针的模板封装类;RefWeak是弱引用包装器类,属性refStrong是强引用对象的指针,refCout是弱引用计数;WeakPtr是对弱引用指针的模板封装类;ObjectImpl表示能被强弱引用的子类对象,即所有需要使用强弱指针的都需要继承于RefObject。

3.1.1 领域问题分析

问题1 : 当RefWeak的refCount减为0要释放的时候,另一个强指针正在执行强引用转弱引用,这样会造成冲突。
为了解决这个问题,我们在RefObject第一次调用getRefWeak构建好RefWeak后会对其addRef,即让RefWeak的弱引用计数加1,后续在delete RefObject时,再对其减一。这样,RefWeak一旦被创建到RefObject被delete时都会一直存在。同时避免环形持有,RefWeak持有RefObject的过程,不能对RefObject执行强引用加1。
问题2 : 当RefObject的引用计数变为0的时候,RefWeak执行了弱引用转强引用(即getRefObject方法),这时也会造成不安全。
为了解决这个问题,我们在调用RefWeak的getRefObject方法的时候,先将refStrong的旧值读到局部变量,如果读取值为0就直接返回一个空的强引用对象指针; 如果读取值为-1就说明其他线程正在执行该方法,就等待直到其不为-1也不为空的时候将refStrong替换为-1(循环处理逻辑当为空的时候就会像上一步那样直接返回空的强引用对象指针); 假设成功替换refStrong为-1后将判断事先读取的refStrong的引用计数是否为0,为零就直接返回一个空的强引用指针,反之不为0,则对其强引用加1;不管是否为零最后都会复原refStrong的值,确保后续线程不会死等;最后当getRefObject返回非空的强引用对象指针时,将该指针对StrongPtr赋值后还需对之前的强引用加1的操作执行强引用减一。同时,在delete RefObject时,若refWeak不为NULL,先是将RefWeak的属性refStrong设置为0,特别地,当refStrong为-1的时候,会等待其被还原;最后才是执行RefWeak的unRef方法。
为了线程安全,采用几个原子操作函数对整个过程(包括问题1和问题2)实现逻辑闭环。

3.2 运行图

3.2.1 对RefObject强引用加一

在这里插入图片描述

3.2.2 对RefObject强引用减一

在这里插入图片描述

3.2.3 强指针转弱指针

在这里插入图片描述

3.2.4 释放RefObject

在这里插入图片描述

3.2.5 对RefWeak弱引用加一

在这里插入图片描述

3.2.6 对RefWeak弱引用减一

在这里插入图片描述

3.2.7 弱引用转强引用

在这里插入图片描述

3.2.8 释放RefWeak

在这里插入图片描述

3.3 代码封装

模板类StrongPtr和WeakPtr的封装,后续我抽空再给大家补充。。。

4 总结

强弱指针根本上没有解决业务问题。如前面的Parent类与Child类的问题,这是我们事先已经知晓他们会造成环形引用。对于一些庞大的系统,以基于插件集成的微核架构模式举例,各个插件之间以接口进行交互,可能A引用B、B引用C、依次类推。。。最后Z又引用A。各个模块又是由不同的人甚至部门在管理,这些开发者或者部门他们自己都不晓得已经形成了环形引用。
为了避免这种问题的发生,可以通过总架构师对业务梳理和把关,针对环形引用的地方通过弱引用去破除。其实,在设计阶段避免环形依赖,即便没有环形引用,也不许模块有环形依赖,哪怕是接口依赖也不许。这样才是根治之道

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值