c++ 转化句柄,解决多线程安全释放问题

cpp 复制代码
#pragma once

#include <iostream>
#include <unordered_map>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <chrono>
#include <memory>
#include <thread>
#include <cstdint>
#include <utility>

template<typename T>
class RefCountNotifier {
public:
	// 构造函数:仅初始化通知器,不持有任何业务层指针的强引用
	RefCountNotifier() : m_isReleased(false) {}

	// 创建业务层的shared_ptr(核心:创建后不保留副本,仅返回给上层)
	std::shared_ptr<T> CreateSharedPtr(T* rawPtr, std::function<void(T*)> deleter) {
		
		if (IsReleased()) {
			return nullptr; // 已释放,拒绝创建
		}

		// 自定义删除器:释放指针时通知等待的线程
		auto customDeleter = [this, origDeleter = std::move(deleter)](T* p) {
			// 1. 执行原始析构逻辑
			if (origDeleter) {
				origDeleter(p);
			}
			// 2. 标记释放并通知
			std::lock_guard<std::mutex> innerLock(m_mutex);
			m_isReleased = true;
			m_cv.notify_all();
			std::cout << "[INFO] 业务层指针引用计数归0,触发释放通知" << std::endl;
		};

		// 3. 创建shared_ptr并返回给上层(不存储为成员,不持有强引用)
		std::shared_ptr<T> ptr(rawPtr, std::move(customDeleter));
	
		return ptr;
	}

	// 等待业务层指针释放(超过5秒打印日志,继续等待)
	void WaitForRelease() {
		std::unique_lock<std::mutex> lock(m_mutex);
		const auto timeout = std::chrono::seconds(5);

		// 循环等待,直到m_isReleased为true(指针已释放)
		while (!m_isReleased) {
			// 等待5秒,超时则打印日志并继续等待
			if (!m_cv.wait_for(lock, timeout, [this]() { return m_isReleased; })) {
				std::cerr << "[WARNING] 释放句柄等待超过5秒,继续等待..." << std::endl;
			}
		}
	}

	// 判断是否已释放
	bool IsReleased() const {
		std::lock_guard<std::mutex> lock(m_mutex);
		return m_isReleased;
	}

private:
	mutable std::mutex m_mutex;               // mutable允许const方法加锁
	std::condition_variable m_cv;             // 释放通知的条件变量
	bool m_isReleased;                        // 标记业务层指针是否已释放
};

template<typename T>
class ConvertHandle {
public:
	// 核心修改:传入二级指针 T**,用于修改上层的裸指针变量
	static int32_t CreateHandle(T** pptr, std::function<void(T*)> deleter = [](T* p) { delete p; }) {
		// 合法性校验:二级指针本身不能为空 + 二级指针指向的裸指针不能为空
		if (!pptr || !*pptr) {
			std::cerr << "[ERROR] 无效指针:二级指针为空或裸指针为空" << std::endl;
			return -1;
		}

		// 1. 保存临时裸指针,用于后续处理(避免置空后丢失原指针地址)
		T* tempPtr = *pptr;
		// 2. 关键步骤:解引用二级指针,将上层的裸指针置为nullptr,使其失效
		*pptr = nullptr;
		std::cout << "[INFO] 上层裸指针已被置空,无法再操作原内存" << std::endl;

		// 3. 调用内部方法处理临时裸指针(逻辑与原有一致)
		return getInstance().CreateHandleInternal(tempPtr, std::move(deleter));
	}

	static int32_t DestroyHandle(int32_t oHandle) {
		if (oHandle <= 0) return -1;
		return getInstance().DestroyHandleInternal(oHandle);
	}

	static std::shared_ptr<T> GetPtr(int32_t oHandle) {
		if (oHandle <= 0) return nullptr;
		return getInstance().GetPtrInternal(oHandle);
	}

private:
	// 辅助结构体:替代C++17的结构化绑定,存储通知器和业务层指针
	struct HandleEntry {
		std::shared_ptr<RefCountNotifier<T>> notifier;
		std::shared_ptr<T> businessPtr;

		// C++11默认构造、移动构造(保证std::move语义正常)
		HandleEntry() = default;
		HandleEntry(std::shared_ptr<RefCountNotifier<T>> n, std::shared_ptr<T> b)
			: notifier(std::move(n)), businessPtr(std::move(b)) {}
		HandleEntry(HandleEntry&& other) noexcept
			: notifier(std::move(other.notifier)), businessPtr(std::move(other.businessPtr)) {}
		HandleEntry& operator=(HandleEntry&& other) noexcept {
			if (this != &other) {
				notifier = std::move(other.notifier);
				businessPtr = std::move(other.businessPtr);
			}
			return *this;
		}

		// 禁止拷贝(保持原有语义)
		HandleEntry(const HandleEntry&) = delete;
		HandleEntry& operator=(const HandleEntry&) = delete;
	};

	int32_t CreateHandleInternal(T* rawPtr, std::function<void(T*)> deleter) {
		// 1. 创建空的通知器(不持有任何指针)
		auto notifier = std::make_shared<RefCountNotifier<T>>();
		// 2. 让通知器创建业务层shared_ptr(仅返回给map存储,通知器不保留)
		auto businessPtr = notifier->CreateSharedPtr(rawPtr, std::move(deleter));
		if (!businessPtr) {
			if (deleter) {
				deleter(rawPtr);
			} else {
				delete rawPtr;
			}
			return -1;
		}

		// 3. 生成句柄并存储(map存储:句柄 → (通知器,业务层指针))
		std::lock_guard<std::mutex> lock(m_mutexHandle);
		int32_t newHandle = 0;
		do {
			m_index = (m_index >= INT32_MAX - 1) ? 1 : m_index + 1;
			newHandle = m_index;
		} while (m_mapHandle.find(newHandle) != m_mapHandle.end());

		// 存储通知器和业务层指针(使用自定义HandleEntry,替代C++17的pair结构化绑定)
		m_mapHandle[newHandle] = HandleEntry(std::move(notifier), std::move(businessPtr));
		return newHandle;
	}

	int32_t DestroyHandleInternal(int32_t oHandle) {
		// 1. 从map中取出并删除(释放map对通知器和业务层指针的引用)
		HandleEntry entry;
		{
			std::lock_guard<std::mutex> lock(m_mutexHandle);
			auto it = m_mapHandle.find(oHandle);
			if (it == m_mapHandle.end()) {
				return -1; // 句柄无效
			}
			// 移动取出entry,避免拷贝
			entry = std::move(it->second);
			m_mapHandle.erase(it);
			std::cout << "[INFO] 句柄" << oHandle << "已从map中移除" << std::endl;

			// 2. 先释放map持有的业务层指针引用(关键!)
			entry.businessPtr.reset();
			std::cout << "[INFO] 释放map持有的业务层指针引用" << std::endl;
		}



		// 3. 等待业务层指针完全释放(此时仅上层持有的指针会影响计数)
		if (!entry.notifier->IsReleased()) {
			std::cout << "[INFO] 开始等待业务层指针释放..." << std::endl;
			entry.notifier->WaitForRelease();
		}

		std::cout << "[INFO] 句柄" << oHandle << "释放完成" << std::endl;
		return 0;
	}

	std::shared_ptr<T> GetPtrInternal(int32_t oHandle) {

		std::shared_ptr<T> businessPtr;
		std::lock_guard<std::mutex> lock(m_mutexHandle);
		auto it = m_mapHandle.find(oHandle);
		if (it == m_mapHandle.end()) {
			return nullptr;
		}

		// C++11不支持结构化绑定,直接访问HandleEntry的成员变量
		const HandleEntry& entry = it->second;

		businessPtr = entry.businessPtr;

		/*
		// 检查是否已释放
		if (notifier->IsReleased()) {
			return nullptr;
		}
		*/
	

		// 返回业务层指针的拷贝(引用计数+1)
		return businessPtr;
	}

	static ConvertHandle<T>& getInstance() {
		static ConvertHandle<T> instance;
		return instance;
	}

private:
	// map存储:句柄 → 自定义HandleEntry(替代原C++17的pair)
	std::unordered_map<int32_t, HandleEntry> m_mapHandle;
	std::mutex m_mutexHandle;
	int32_t m_index = 0;

	// 禁止拷贝
	ConvertHandle() = default;
	ConvertHandle(const ConvertHandle&) = delete;
	ConvertHandle& operator=(const ConvertHandle&) = delete;
};

#include <iostream>
// 测试代码:验证逻辑正确性
int main() {
	// 测试用的业务类
	class TestObj {
	public:
		~TestObj() {
			std::cout << "[INFO] TestObj 析构函数执行(真正释放资源)" << std::endl;
		}
	};

	// 1. 创建句柄
	TestObj* obj = new TestObj();
	int32_t handle = ConvertHandle<TestObj>::CreateHandle(&obj);
	std::cout << "[INFO] 创建句柄:" << handle << std::endl;

	// 2. 上层获取指针(引用计数+1)
	auto upperPtr1 = ConvertHandle<TestObj>::GetPtr(handle);
	std::cout << "[INFO] 上层第一次获取指针,引用计数:" << upperPtr1.use_count() << std::endl;

	// 3. 模拟另一个线程持有指针6秒后释放
	std::thread t([&]() {
		auto upperPtr2 = ConvertHandle<TestObj>::GetPtr(handle);
		std::cout << "[INFO] 子线程获取指针,引用计数:" << upperPtr2.use_count() << std::endl;
		std::cout << "[INFO] 子线程持有指针6秒..." << std::endl;
		std::this_thread::sleep_for(std::chrono::seconds(6));
		upperPtr2.reset();
		std::cout << "[INFO] 子线程释放指针" << std::endl;
		});

	// 5. 释放上层最后一个指针
	upperPtr1.reset();

	// 4. 主线程立即销毁句柄(会等待,超过5秒打印日志)
	std::cout << "[INFO] 主线程开始销毁句柄..." << std::endl;
	int ret = ConvertHandle<TestObj>::DestroyHandle(handle);
	std::cout << "[INFO] 句柄销毁结果:" << ret << std::endl;

	
	std::cout << "[INFO] 主线程释放最后一个指针" << std::endl;

	// 等待子线程结束
	t.join();
	return 0;
}

功能和效果

1: 可以把指针转化成句柄,通过句柄来管理释放,保证创建,使用,释放实现多线程安全。

2:代码性能基本高效,unordered_map 的时间复杂度平均O(1)

3:可以很方便改造已有的代码

相关推荐
冰暮流星2 小时前
javascript如何转换为字符串与布尔型
java·开发语言·javascript
youqingyike2 小时前
Qt 中 QWidget 调用setLayout 后不显示
开发语言·c++·qt
_OP_CHEN2 小时前
【从零开始的Qt开发指南】(二十二)Qt 音视频开发宝典:从音频播放到视频播放器的实战全攻略
开发语言·c++·qt·音视频·前端开发·客户端开发·gui开发
FAFU_kyp2 小时前
Rust 字符串与切片
开发语言·后端·rust
oioihoii2 小时前
从C++到C#的转型完全指南
开发语言·c++·c#
Ashley_Amanda2 小时前
Python入门知识点梳理
开发语言·windows·python
区区一散修2 小时前
Java进阶 6. 集合
java·开发语言
学嵌入式的小杨同学2 小时前
C 语言实战:动态规划求解最长公共子串(连续),附完整实现与优化
数据结构·c++·算法·unity·游戏引擎·代理模式
-凌凌漆-2 小时前
【JS】JavaScript Promise
开发语言·javascript·ecmascript