在多线程环境下,智能指针(如std::shared_ptr)通过引用计数机制实现资源共享与自动销毁。然而,默认的实现并不是线程安全的:若多个线程同时操作同一智能指针实例,引用计数的递增/递减可能出现竞争。为满足高并发需求,可自行实现一个线程安全的智能指针。下面给出一种常见的实现思路与关键细节。
1. 设计原则
- 数据独立:智能指针内部的数据结构(引用计数、控制块等)需要在多线程之间保持一致性。
- 轻量化:不希望在每一次复制、销毁时都产生额外的锁或内存开销。
- 可扩展:后续可对控制块进行额外扩展(如自定义析构、可观察对象等)。
2. 控制块(Control Block)
struct ControlBlock {
std::atomic <size_t> use_count; // 共享引用计数
std::atomic <size_t> weak_count; // 弱引用计数
void* deleter; // 自定义删除器
void* data; // 原始指针
};
- `std::atomic ` 用于保证递增/递减操作的原子性。
deleter可以是std::function<void(void*)>或自定义函数指针。data存放真实对象指针。
3. 智能指针类
template <typename T>
class ThreadSafeSharedPtr {
public:
explicit ThreadSafeSharedPtr(T* ptr = nullptr) {
if (ptr) {
ctrl_ = new ControlBlock{1, 0, nullptr, ptr};
}
}
ThreadSafeSharedPtr(const ThreadSafeSharedPtr& other) noexcept {
acquire(other.ctrl_);
}
ThreadSafeSharedPtr(ThreadSafeSharedPtr&& other) noexcept {
ctrl_ = other.ctrl_;
other.ctrl_ = nullptr;
}
~ThreadSafeSharedPtr() {
release();
}
ThreadSafeSharedPtr& operator=(const ThreadSafeSharedPtr& other) noexcept {
if (this != &other) {
release();
acquire(other.ctrl_);
}
return *this;
}
ThreadSafeSharedPtr& operator=(ThreadSafeSharedPtr&& other) noexcept {
if (this != &other) {
release();
ctrl_ = other.ctrl_;
other.ctrl_ = nullptr;
}
return *this;
}
T* get() const noexcept { return ctrl_ ? static_cast<T*>(ctrl_->data) : nullptr; }
T& operator*() const noexcept { return *get(); }
T* operator->() const noexcept { return get(); }
size_t use_count() const noexcept { return ctrl_ ? ctrl_->use_count.load(std::memory_order_acquire) : 0; }
explicit operator bool() const noexcept { return get() != nullptr; }
private:
ControlBlock* ctrl_ = nullptr;
void acquire(ControlBlock* cb) noexcept {
ctrl_ = cb;
if (ctrl_) ctrl_->use_count.fetch_add(1, std::memory_order_acq_rel);
}
void release() noexcept {
if (ctrl_) {
if (ctrl_->use_count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
// 最后一个共享引用,销毁对象
if (ctrl_->deleter) {
// 自定义删除器
auto d = reinterpret_cast<void(*)(void*)>(ctrl_->deleter);
d(ctrl_->data);
} else {
delete static_cast<T*>(ctrl_->data);
}
// 处理弱引用计数
if (ctrl_->weak_count.load(std::memory_order_acquire) == 0) {
delete ctrl_;
}
}
ctrl_ = nullptr;
}
}
};
关键点说明
- 构造函数:若传入非空指针,则创建新的控制块,
use_count初始化为1。 - 拷贝构造:调用
acquire,把计数加1。 - 移动构造:直接转移控制块指针,避免计数变化。
- 析构:调用
release,计数减1。若计数降到0,则销毁对象。随后检查弱引用计数,若为0,则删除控制块。 - 计数操作:所有对计数的递增/递减都使用
std::atomic并指定内存序保证可见性。
4. 线程安全性验证
#include <thread>
#include <iostream>
#include <vector>
struct Demo {
int value;
Demo(int v) : value(v) { std::cout << "Demo constructed\n"; }
~Demo() { std::cout << "Demo destructed\n"; }
};
int main() {
ThreadSafeSharedPtr <Demo> ptr(new Demo(42));
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
ThreadSafeSharedPtr <Demo> local = ptr; // 拷贝
threads.emplace_back([local]() mutable {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "Thread use_count: " << local.use_count() << "\n";
});
}
for (auto& t : threads) t.join();
std::cout << "Main finished\n";
}
运行时可观察到:
Demo的构造一次,析构一次。use_count在每个线程中均保持正确,说明引用计数操作无竞争。
5. 可选扩展
- 弱引用:实现 `ThreadSafeWeakPtr `,维护 `weak_count` 并支持 `lock()`。
- 自定义分配器:将
ControlBlock交由自定义分配器管理,减少碎片。 - 异常安全:在构造时若分配失败,确保不泄露资源。
6. 与 std::shared_ptr 的比较
- 性能:自定义实现的
std::atomic计数比标准库内部的std::atomic更透明,但可能略慢于编译器内置优化。 - 功能:缺乏标准库的多功能性(如
enable_shared_from_this、make_shared的优化)。 - 可维护性:标准库经过多年实践与测试,稳定可靠;自定义实现需自行维护。
7. 结语
在需要自定义删除器或特殊内存管理策略的高并发项目中,手写线程安全的智能指针可以为你提供更细粒度的控制。通过上述示例,你可以在此基础上进一步完善,加入弱引用、内存池、或者更复杂的生命周期管理,以满足项目需求。