C++11:构建多线程环境下的资源管理器

应用场景

我们在多线程下开发时,经常会遇到这样一个场景,有一种资源,它是有限的n个,每个资源每次只能由一个线程独占使用。
就好像一个公共厕所,蹲坑是有限的,对于无限多个要上厕所的人来说,如果蹲坑满了大家只能排队上厕所,如果大家都不守规矩要抢着上厕所,那么门口就得站个管理员来维持秩序,每从当厕所中有空闲的坑位时,就放一个人进来,指定他用这个空闲的坑位,等再有坑位空出来就再放一个人进来,否则铁定乱套。
这里,这个厕所管理员就好比一个资源管理器,管理着所有资源,所有的线程都要向这个管理员申请才能获取所要的资源。

完整代码

下面代实现的resource_manager,就是基于这个应用需求实现的c++11模板类。

/*
 * resource_manager.h
 *
 *  Created on: 2016年7月25日
 *      Author: guyadong
 */

#ifndef COMMON_SOURCE_CPP_RESOURCE_MANAGER_H_
#define COMMON_SOURCE_CPP_RESOURCE_MANAGER_H_
#include <thread>
#include <type_traits>
#include <memory>
#include <vector>
#include <queue>
#include <utility>
#include <initializer_list>
#include "assert_macros.h"
#include "threadsafe_queue.h"
#include "threadsafe_unordered_map.h"
#include "raii.h"
namespace gdface {
/*
 * 多线程类空间
 */
inline namespace mt{
/*
* 无资源异常
* 当资源数为0时抛出此异常时
* */
class no_resource_except :public std::logic_error {
public:
	// 继承基类构造函数
	using std::logic_error::logic_error;
	no_resource_except(const std::string &at, const std::string &msg) :logic_error(std::string(at).append(":").append(msg)) {}
	no_resource_except(const std::string &at, const std::exception&e) :logic_error(std::string(at).append(":").append(e.what())) {}
	no_resource_except(const std::string &at, const std::exception&e, const std::string &msg) :logic_error(std::string(at).append(":").append(e.what()).append(":").append(msg)) {}
};
/*
 * 多线程环境共享资源管理类
 * 禁止移动/复制构造函数
 * 禁止移动/复制赋值操作符
 * 所有被管理的资源(R)存放在数组中
 * acquire申请资源,当无资源可用时阻塞
 * release释放资源
 * 同一线程内允许嵌套执行acquire/release
 * acquire/release必须配对使用,否则会造成资源泄漏
 * */
template<typename R>
class resource_manager {
private:
	// 资源索引类型
	using resource_index_type=size_t;
	// 资源队列类型
	using resource_queue_type=threadsafe_queue<resource_index_type>;
public:
	// 返回类型,R为标量类型时直接返回R的值,否则返回引用
	using return_type=typename std::conditional<std::is_scalar<R>::value,R,R&>::type;
	resource_manager()=default;
	/* 禁止复制构造 */
	resource_manager(const resource_manager&)=delete;
	/* 禁止移动构造 */
	resource_manager(resource_manager&&)=delete;
	/* 禁止复制赋值 */
	resource_manager&operator=(const resource_manager&)=delete;
	/* 禁止移动赋值 */
	resource_manager&operator=(resource_manager&&)=delete;
	/*
	* 基本构造函数,
	* 使用迭代器为参数的构造函数,适用所有容器对象,
	* lock_count初始化为0
	* occupy_map初始化为空
	* free_queue中包含所有资源索引
	* */
	template<typename _InputIterator>
	resource_manager(_InputIterator first, _InputIterator last) :
		resource(first, last)
		,lock_count(resource.size(), typename decltype(lock_count)::value_type(0))
		,occupy_thread()
		,free_queue(make_free_queue(resource.size())){
	}
	/*
	 * 对于类型为整数的资源,提供一个简便的构造函数
	 * count 资源数目
	 * start 整数起始值
	 * 根据这两个参数构建一个start开始count个整数作为资源数组
	 * */
	template<typename Enable=typename std::enable_if<std::is_integral<R>::value>::type>
	resource_manager(size_t count,R start=0):resource_manager(make_vector<R>(count,start)){}
	/*
	 * std::vector类型的资源数组为参数的构造函数
	 * */
	resource_manager(const std::vector<R>& res):resource_manager(res.begin(),res.end()){}
	/*
	 * 使用初始化列表为参数的构造函数
	 * */
	resource_manager(std::initializer_list<R> list):resource_manager(list.begin(),list.end()){}

	virtual ~resource_manager(){
		// 将资源数组清空,如果还有线程请求资源会导致抛出no_resource_except异常
		resource.clear();
	}

	/*
	 * 返回一个自动化的资源管理对象(不可跨线程使用)
	 * raii_var对象构造时会自动申请资源
	 * raii_var对象析构时会自动释放资源
	 * raii_var对象的生命周期必须在当前对象生命周期内,否则在执行资源释放时this指针无效
	 * */
	raii_var<return_type> resource_guard(){
		return raii_var<return_type>(
				[this]{return this->acquire();},
				[this](return_type){this->release();}
			);
	}
private:
	std::vector<R> resource;
	// 占用资源的线程中的加锁计数
	std::vector<size_t> lock_count;
	// 保存每个占用资源的线程id和所占用资源索引的映射,初始为空
	threadsafe_unordered_map<std::thread::id,resource_index_type> occupy_thread;
	// 空闲资源(索引)队列,队列中保存的是资源在resource中的索引,初始为resource全部索引
	std::shared_ptr<resource_queue_type> free_queue;
	template<typename Enable=typename std::enable_if<std::is_integral<R>::value>::type>
	static std::vector<R>
	make_vector(size_t count,R start=0){
		std::vector<R> v(count);
		for(size_t i=0;i<count;++i){
			v[i]=R(i+start);
		}
		return v;
	}
	/*
	 * 创建并初始化资源索引队列,将所有资源索引加入队列
	 * */
	static std::shared_ptr<resource_queue_type>
	make_free_queue(size_t size) {
		using v_type = typename resource_queue_type::value_type;
		std::vector<v_type>v(size);
		//创建索引数组
		for (size_t i = 0; i<size; ++i) { v[i] = v_type(i); }
		return std::make_shared<resource_queue_type>(v.begin(), v.end());;
	}
	/*
	 * 阻塞方式从队列中获取可用的资源
	 * 资源数为0时抛出no_resource_except异常
	 */
	return_type acquire(){
		auto this_thread_id=std::this_thread::get_id();
		resource_index_type resource_index;
		// 当前线程重复加锁时不需要再申请资源,将加lock_cout+1,然后返指定的对象
		occupy_thread.insertIfAbsent(this_thread_id,resource_index);
		occupy_thread.replace(this_thread_id,resource_index);
		occupy_thread.replace(this_thread_id,resource_index,resource_index);
		if(!occupy_thread.find(this_thread_id,resource_index)){
			// 向空闲队列申请资源
			resource_index=free_queue->wait_and_pop();
			// 状态不对常抛出异常throw_except_if_msg(std::logic_error,0>resource_index||0!=lock_count[resource_index],"invalid resource status");
			// 将申请到的资源索引加入线程占用表,代表当前线程已经使用了这个资源
			occupy_thread.insert({this_thread_id,resource_index});
		}
		++lock_count[resource_index];
		// 资源数目为0时抛出异常
		throw_except_if(no_resource_except,resource.empty());
		return resource[resource_index];
	}
	/*
	 * 释放资源
	 */
	void release(){
		resource_index_type resource_index;
		auto thread_id=std::this_thread::get_id();
		// 状态不对常抛出异常
		throw_except_if_msg(std::logic_error,
				!occupy_thread.find(thread_id,resource_index)||resource_index>=lock_count.size()||lock_count[resource_index]<=0
				,"invalid acquire/release nest");
		--lock_count[resource_index];
		// 当前线程所有嵌套解锁后才将资源重新加入free_queue
		if(0==lock_count[resource_index]){
			// 从线程map中删除当前线程
			occupy_thread.erase(thread_id);
			// 将释放出来的索引号加入free队列
			free_queue->push(resource_index);
		}
	}
};
}/* namespace mt */
} /* namespace gdface */
#endif /* COMMON_SOURCE_CPP_RESOURCE_MANAGER_H_ */

原理说明

这个类的主要原理是将资源®存储在数组(std::vector)中,用一个队列(threadsafe_queue)来管理所有空闲的资源索引(free_queue)。
线程每次调用acquire函数从空闲资源队列(free_queue)中获取一个资源,如果队列为空就阻塞。
线程使用完资源后调用release函数将资源重回加入队列,并唤醒等待资源的线程。
同一个线程多次调用acquire不会重复申请资源,只会将已经申请的资源对应的引用计数(lock_count)加1,同一个线程多次调用release不会重复释放资源,只会将已经申请的资源对应的引用计数(lock_count)减1,直到计数器为0再将资源放回空闲资源队列。

代码中用于管理空闲资源的队列用到的threadsafe_queue类参见我之前的博客《C++11:基于std::queue和std::mutex构建一个线程安全的队列》
代码中用到的threadsafe_unordered_map参见我之前的博客《C++11:基于std::unordered_map和共享锁构建线程安全的map》
代码用到的raii类参见我之前的博客《C++11实现模板化(通用化)RAII机制》

使用示例

类中除了构造函数之外只有一个公开的resource_guard函数。这个函数返回的raii类自动完成了资源申请和释放的动作,所以资源的使用非常简单,调用代码根本不用关心资源的申请和释放。
下面是代码示例片段

using channel_type =short;
using channel_manger_type=mt::resource_manager<channel_type>;
// 人脸检测通道管理器
std::unique_ptr<channel_manger_type> detect_channel_mgr;
JNIEXPORT jobjectArray JNICALL Java_net_facesdk_cas_JNIBridge_detectFace
  (JNIEnv *env, jclass, jbyteArray jImgData, jint bpp, jint nWidth, jint nHeight){
	....
	// 调用resource_guard函数获取通道资源
	// 资源的申请和释放都已经被RAII对象自动完成了,
	// 这里调用代码只管调用get函数返回所要的资源就行了,不用考虑资源申请和释放的问题
	auto face_num=THFI_DetectFace(detect_channel_mgr->resource_guard().get(), (BYTE*)(*raii_byte_ptr), bpp, nWidth, nHeight, get_face_pos_buf().get().data(), int(get_face_pos_buf().get().size()));
	...
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10km

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值