前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++11:构建多线程环境下的资源管理器

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

作者头像
10km
发布2019-05-25 22:14:20
4760
发布2019-05-25 22:14:20
举报
文章被收录于专栏:10km的专栏10km的专栏

版权声明:本文为博主原创文章,转载请注明源地址。 https://cloud.tencent.com/developer/article/1433604

应用场景

我们在多线程下开发时,经常会遇到这样一个场景,有一种资源,它是有限的n个,每个资源每次只能由一个线程独占使用。

就好像一个公共厕所,蹲坑是有限的,对于无限多个要上厕所的人来说,如果蹲坑满了大家只能排队上厕所,如果大家都不守规矩要抢着上厕所,那么门口就得站个管理员来维持秩序,每从当厕所中有空闲的坑位时,就放一个人进来,指定他用这个空闲的坑位,等再有坑位空出来就再放一个人进来,否则铁定乱套。

这里,这个厕所管理员就好比一个资源管理器,管理着所有资源,所有的线程都要向这个管理员申请才能获取所要的资源。

完整代码

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

代码语言:javascript
复制
/*
 * 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_ */

原理说明

这个类的主要原理是将资源(R)存储在数组(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类自动完成了资源申请和释放的动作,所以资源的使用非常简单,调用代码根本不用关心资源的申请和释放。

下面是代码示例片段

代码语言:javascript
复制
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()));
    ...
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年07月30日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 应用场景
  • 完整代码
  • 原理说明
  • 使用示例
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档