Async++ 源码分析10--ref_count.h
一、Async++ 代码目录结构
Async++ 项目的目录结构清晰,主要包含根目录下的配置文件、源代码目录、头文件目录以及示例代码目录,具体结构如下:
asyncplusplus/
├── .gitignore # Git 忽略文件配置
├── Async++Config.cmake.in # CMake 配置模板文件
├── CMakeLists.txt # CMake 构建脚本
├── LICENSE # 许可证文件(MIT 许可证)
├── README.md # 项目说明文档
├── examples/ # 示例代码目录
│ └── gtk_scheduler.cpp # GTK 调度器示例
├── src/ # 源代码目录
│ ├── fifo_queue.h # FIFO 队列实现
│ ├── internal.h # 内部头文件(包含类型定义、宏等)
│ ├── scheduler.cpp # 调度器实现
│ ├── singleton.h # 单例模式实现
│ ├── task_wait_event.h # 任务等待事件实现
│ ├── threadpool_scheduler.cpp # 线程池调度器实现
│ └── work_steal_queue.h # 工作窃取队列实现
└── include/ # 头文件目录├── async++.h # 主头文件(对外提供统一接口)└── async++/ # 子模块头文件目录├── aligned_alloc.h├── cancel.h├── continuation_vector.h├── parallel_for.h├── parallel_invoke.h├── parallel_reduce.h├── partitioner.h # 分区器相关定义├── range.h # 范围(迭代器对)相关定义├── ref_count.h├── scheduler.h # 调度器接口定义├── scheduler_fwd.h├── task.h # 任务类定义├── task_base.h # 任务基类定义├── traits.h└── when_all_any.h
二、ref_count.h源码分析
2.1 源码
// Copyright (c) 2015 Amanieu d'Antras
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.#ifndef ASYNCXX_H_
# error "Do not include this header directly, include <async++.h> instead."
#endifnamespace async {
namespace detail {// Default deleter which just uses the delete keyword
template<typename T>
struct default_deleter {static void do_delete(T* p){delete p;}
};// Reference-counted object base class
template<typename T, typename Deleter = default_deleter<T>>
struct ref_count_base {std::atomic<std::size_t> ref_count;// By default the reference count is initialized to 1explicit ref_count_base(std::size_t count = 1): ref_count(count) {}void add_ref(std::size_t count = 1){ref_count.fetch_add(count, std::memory_order_relaxed);}void remove_ref(std::size_t count = 1){if (ref_count.fetch_sub(count, std::memory_order_release) == count) {std::atomic_thread_fence(std::memory_order_acquire);Deleter::do_delete(static_cast<T*>(this));}}void add_ref_unlocked(){ref_count.store(ref_count.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed);}bool is_unique_ref(std::memory_order order){return ref_count.load(order) == 1;}
};// Pointer to reference counted object, based on boost::intrusive_ptr
template<typename T>
class ref_count_ptr {T* p;public:// Note that this doesn't increment the reference count, instead it takes// ownership of a pointer which you already own a reference to.explicit ref_count_ptr(T* t): p(t) {}ref_count_ptr(): p(nullptr) {}ref_count_ptr(std::nullptr_t): p(nullptr) {}ref_count_ptr(const ref_count_ptr& other) LIBASYNC_NOEXCEPT: p(other.p){if (p)p->add_ref();}ref_count_ptr(ref_count_ptr&& other) LIBASYNC_NOEXCEPT: p(other.p){other.p = nullptr;}ref_count_ptr& operator=(std::nullptr_t){if (p)p->remove_ref();p = nullptr;return *this;}ref_count_ptr& operator=(const ref_count_ptr& other) LIBASYNC_NOEXCEPT{if (p) {p->remove_ref();p = nullptr;}p = other.p;if (p)p->add_ref();return *this;}ref_count_ptr& operator=(ref_count_ptr&& other) LIBASYNC_NOEXCEPT{if (p) {p->remove_ref();p = nullptr;}p = other.p;other.p = nullptr;return *this;}~ref_count_ptr(){if (p)p->remove_ref();}T& operator*() const{return *p;}T* operator->() const{return p;}T* get() const{return p;}T* release(){T* out = p;p = nullptr;return out;}explicit operator bool() const{return p != nullptr;}friend bool operator==(const ref_count_ptr& a, const ref_count_ptr& b){return a.p == b.p;}friend bool operator!=(const ref_count_ptr& a, const ref_count_ptr& b){return a.p != b.p;}friend bool operator==(const ref_count_ptr& a, std::nullptr_t){return a.p == nullptr;}friend bool operator!=(const ref_count_ptr& a, std::nullptr_t){return a.p != nullptr;}friend bool operator==(std::nullptr_t, const ref_count_ptr& a){return a.p == nullptr;}friend bool operator!=(std::nullptr_t, const ref_count_ptr& a){return a.p != nullptr;}
};} // namespace detail
} // namespace async
这段代码是 Async++ 框架中侵入式引用计数智能指针的核心实现,位于 async::detail::
命名空间,包含两个核心组件:ref_count_base
(引用计数基类)和 ref_count_ptr
(侵入式智能指针)。其核心功能是通过 “对象自身存储引用计数” 的方式,实现动态内存的安全管理,避免内存泄漏和野指针问题,广泛用于框架中任务对象(如 task_base
)的生命周期管理。以下是分层解析:
2.2. 核心概念铺垫
引用计数智能指针分为两类,Async++ 实现的是侵入式(Intrusive) 智能指针:
- 侵入式:引用计数存储在对象内部(通过继承
ref_count_base
实现),对象本身感知引用计数变化; - 非侵入式:引用计数存储在智能指针内部(如
std::shared_ptr
),对象无需修改,智能指针单独管理计数。
侵入式的优势是内存开销低(无需额外为计数分配内存)、对象复用性强(适合框架中频繁创建 / 销毁的任务对象),但要求对象必须继承自引用计数基类。
2.3. 核心组件解析
代码分为 “引用计数基类” 和 “智能指针封装” 两层,基类负责计数管理,智能指针负责提供友好的接口并自动维护计数。
(1)引用计数基类:ref_count_base
ref_count_base<T, Deleter>
是模板基类,供框架中的对象(如任务对象 task_base
)继承,内部存储原子引用计数,并提供计数增减、对象销毁等核心方法。
模板参数说明
参数名 | 作用 |
---|---|
T | 子类类型(用于 static_cast 正确销毁对象,需满足 “子类继承自 ref_count_base<T> ”) |
Deleter | 销毁器(默认 default_deleter<T> ,用 delete 销毁对象,支持自定义销毁逻辑) |
核心成员与方法
template<typename T, typename Deleter = default_deleter<T>>
struct ref_count_base {// 原子引用计数:确保多线程下计数修改的线程安全std::atomic<std::size_t> ref_count;// 1. 构造函数:默认初始化引用计数为 1(对象创建时,初始拥有 1 个引用)explicit ref_count_base(std::size_t count = 1) : ref_count(count) {}// 2. 增加引用计数(线程安全)void add_ref(std::size_t count = 1) {// 内存序:relaxed(仅需保证计数原子性,无其他内存依赖)ref_count.fetch_add(count, std::memory_order_relaxed);}// 3. 减少引用计数,计数为 0 时销毁对象(核心方法)void remove_ref(std::size_t count = 1) {// 步骤1:原子减少计数,返回减少前的旧值// 内存序:release(确保对象销毁前,所有对该对象的修改已完成)if (ref_count.fetch_sub(count, std::memory_order_release) == count) {// 步骤2:若旧值 == 减少的计数 → 计数变为 0,需要销毁对象// 内存序:acquire(确保销毁前,所有线程对对象的访问已结束)std::atomic_thread_fence(std::memory_order_acquire);// 步骤3:调用销毁器删除对象(默认用 delete,支持自定义)Deleter::do_delete(static_cast<T*>(this));}}// 4. 无锁增加计数(仅在已加锁场景使用,非线程安全)void add_ref_unlocked() {// 先加载计数,加 1 后存储(无原子操作,需外部保证线程安全)ref_count.store(ref_count.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed);}// 5. 判断是否为唯一引用(用于优化,如“唯一引用时可修改对象”)bool is_unique_ref(std::memory_order order) {return ref_count.load(order) == 1;}
};
关键逻辑:remove_ref
的线程安全销毁
remove_ref
是最核心的方法,确保 “仅当最后一个引用被释放时,才销毁对象”,且多线程安全:
- 原子操作:用
std::atomic::fetch_sub
原子减少计数,避免多线程下计数计算错误; - 内存序:
release
:确保当前线程对对象的所有修改,在对象销毁前已被其他线程可见;acquire
:确保销毁对象时,其他线程对对象的所有访问已结束;
- 销毁器:通过
Deleter::do_delete
销毁,支持自定义(如对象在共享内存中,需特殊释放逻辑)。
默认销毁器:default_deleter
default_deleter<T>
是默认销毁器,仅封装 delete
操作,适用于大多数场景:
template<typename T>
struct default_deleter {static void do_delete(T* p) {delete p; // 用 delete 销毁对象(需确保对象由 new 分配)}
};
(2)侵入式智能指针:ref_count_ptr
ref_count_ptr<T>
是模板智能指针,封装了 ref_count_base
子类的指针,提供与 std::shared_ptr
类似的接口(如 operator*
、operator->
),并自动维护引用计数(构造时加计数,析构时减计数)。
核心设计原则
- 所有权传递:通过构造 / 赋值操作,自动传递对象的引用权,避免手动调用
add_ref
/remove_ref
; - 线程安全:依赖
ref_count_base
中的原子计数,确保多线程下智能指针操作的安全性; - 零额外开销:仅存储一个裸指针(
T* p
),无其他成员,内存开销与裸指针一致。
核心方法解析
template<typename T>
class ref_count_ptr {T* p; // 指向 ref_count_base<T> 子类的裸指针public:// 1. 构造函数:// - 裸指针构造:不增加计数(需用户保证已拥有引用权,如对象刚 new 出时)explicit ref_count_ptr(T* t) : p(t) {}// - 默认构造:空指针,计数无意义ref_count_ptr() : p(nullptr) {}// - 拷贝构造:增加计数(共享所有权)ref_count_ptr(const ref_count_ptr& other) LIBASYNC_NOEXCEPT : p(other.p) {if (p) p->add_ref(); // 其他指针非空,增加当前对象的引用计数}// - 移动构造:不增加计数(转移所有权,原指针置空)ref_count_ptr(ref_count_ptr&& other) LIBASYNC_NOEXCEPT : p(other.p) {other.p = nullptr; // 原指针失去所有权,避免析构时重复减计数}// 2. 赋值运算符:// - 拷贝赋值:先减当前计数(若非空),再共享新对象并加计数ref_count_ptr& operator=(const ref_count_ptr& other) LIBASYNC_NOEXCEPT {if (p) { p->remove_ref(); p = nullptr; } // 释放当前对象p = other.p;if (p) p->add_ref(); // 共享新对象,加计数return *this;}// - 移动赋值:先减当前计数,再转移新对象所有权(原指针置空)ref_count_ptr& operator=(ref_count_ptr&& other) LIBASYNC_NOEXCEPT {if (p) { p->remove_ref(); p = nullptr; }p = other.p;other.p = nullptr;return *this;}// - 空指针赋值:释放当前对象,置空ref_count_ptr& operator=(std::nullptr_t) {if (p) { p->remove_ref(); p = nullptr; }return *this;}// 3. 析构函数:自动减少计数,计数为 0 时销毁对象~ref_count_ptr() {if (p) p->remove_ref();}// 4. 指针操作接口(与裸指针兼容)T& operator*() const { return *p; } // 解引用T* operator->() const { return p; } // 成员访问T* get() const { return p; } // 获取裸指针T* release() { // 释放所有权(不减少计数)T* out = p; p = nullptr; return out;}// 5. 类型转换与比较explicit operator bool() const { return p != nullptr; } // 判空// 与其他智能指针/空指针比较(基于裸指针地址)friend bool operator==(const ref_count_ptr& a, const ref_count_ptr& b) { return a.p == b.p; }friend bool operator!=(const ref_count_ptr& a, const ref_count_ptr& b) { return a.p != b.p; }
};
2.4. 框架中的典型用法
Async++ 中,ref_count_ptr
主要用于管理任务对象(如 task_base
) 的生命周期,因为任务可能被多个线程引用(如主线程提交任务、线程池执行任务、其他任务依赖该任务),需要安全的引用计数管理。
示例:任务对象继承 ref_count_base
// 1. 任务基类:继承自 ref_count_base,拥有引用计数
class task_base : public async::detail::ref_count_base<task_base> {
public:virtual ~task_base() = default;virtual void execute() const = 0; // 任务执行接口
};// 2. 具体任务类:继承自 task_base
class PrintTask : public task_base {
public:void execute() const override {std::cout << "Task executed\n";}
};// 3. 用 ref_count_ptr 管理任务生命周期
int main() {// 步骤1:创建任务对象,初始引用计数为 1task_base* raw_task = new PrintTask();// 步骤2:用 ref_count_ptr 接管,此时计数仍为 1(裸指针构造不增加计数)async::detail::ref_count_ptr<task_base> task_ptr(raw_task);// 步骤3:拷贝智能指针,计数增加到 2async::detail::ref_count_ptr<task_base> task_ptr2 = task_ptr;// 步骤4:多线程共享任务(无需手动管理计数)std::thread t([task_ptr2]() {task_ptr2->execute(); // 线程安全访问任务});t.join();// 步骤5:智能指针析构时自动减少计数// - task_ptr2 析构:计数从 2 → 1// - task_ptr 析构:计数从 1 → 0,任务对象被 delete 销毁return 0;
}
2.5. 关键设计亮点
(1)线程安全的计数管理
通过 std::atomic<std::size_t>
存储引用计数,add_ref
/remove_ref
用原子操作确保多线程下计数修改的正确性,避免 “计数竞态” 导致的内存泄漏或重复销毁。
(2)内存高效
- 侵入式设计:计数存储在对象内部,无需额外分配内存(对比
std::shared_ptr
需额外分配 “控制块” 存储计数); - 智能指针轻量:
ref_count_ptr
仅存储一个裸指针,大小与裸指针一致(32 位系统 4 字节,64 位系统 8 字节)。
(3)灵活的销毁策略
支持自定义 Deleter
,可应对特殊场景(如:
- 对象在栈上:
Deleter
为空操作,不调用delete
; - 对象在共享内存:
Deleter
调用共享内存释放接口。
(4)兼容裸指针接口
提供 get()
/release()
/operator*
/operator->
等接口,用法与裸指针、std::shared_ptr
一致,降低用户学习成本。
5. 注意事项
(1)对象必须继承自 ref_count_base
ref_count_ptr<T>
仅能管理继承自 ref_count_base<T>
的对象,否则调用 add_ref
/remove_ref
会编译失败(类型不匹配)。
(2)裸指针构造的安全性
用裸指针 T*
构造 ref_count_ptr
时,必须确保该裸指针指向的对象引用计数已初始化为 1(如通过 new
创建的 ref_count_base
子类),否则会导致计数错误(如计数为 0 时,remove_ref
会立即销毁对象)。
(3)避免循环引用
与 std::shared_ptr
类似,ref_count_ptr
也会产生循环引用问题(如 A 引用 B,B 引用 A,两者计数永远不为 0,导致内存泄漏),需通过 “弱引用”(框架未实现,需手动设计)避免。
3. 总结
Async++ 的 ref_count_base
和 ref_count_ptr
是侵入式引用计数智能指针的高效实现,核心价值在于:
- 为框架中的任务对象提供线程安全的生命周期管理;
- 内存开销低、接口友好,兼容 C++ 标准智能指针的使用习惯;
- 支持自定义销毁策略,适配不同内存分配场景。
该实现是框架并发安全的基础之一,确保多线程下任务对象的创建、共享、销毁不会出现内存问题,同时兼顾性能与灵活性。