C++连接池练习
如何使用USB-C转接头连接不同设备 #生活技巧# #数码产品使用技巧# #3C配件维护#
连接池
1. 特点
池内连接默认创建行为一致,所以可以使用工厂注入,在连接池构造时初始化好连接
减少连接创建过程,预创建好连接,提供申请、归还接口,用户只需进行申请、归还操作,期间没有重复创建
连接池管理连接生命周期,连接如何释放的问题
使用智能指针管理,内部unique_ptr, 申请出去的连接使用shared_ptr管理 对于申请出去的连接, shared_ptr自定义销毁函数,在用户不使用连接时(连接离开作用域)自动触发, shared_ptr的自定义销毁函数触发时要处理以下情况, 一是正常归还,需要release控制权,重新创建连接池的unique_ptr类型, 同时需要注意删除器要使用最开始工厂创建unique_ptr的删除器, 因为有可能是自定义删除器 二是归还的时候,连接池可能已经析构了,这时候要shared_ptr自己处理连接的析构问题,但是连接删除器(注意区分shared_ptr的删除器)可能是自定义的,所以shared_ptr的自定义删除器要捕获连接的删除器,在归还时,如果连接池析构,就使用连接的删除器进行删除 如果连接池有动态扩容、缩容功能,回收连接时也可能不放入池子而直接释放,这时也需要shared_ptr的自定义删除器来进行处理,实质等价于第2.设置最大连接数,初始连接数,期间可以动态扩容
如果连接异常,怎么处理? 需要注入链接状态检查器,检查连接是否可以,申请时检查、归还时检查
连接统计信息,总连接数、空闲连接数、活跃连接数、目前有多少接口在申请等
#ifndef __CHAT_CONNECTION_POOL_H__ #define __CHAT_CONNECTION_POOL_H__ #include "chat/uncopyable.h" #include <list> #include <memory> #include <mutex> #include <condition_variable> #include <atomic> #include <stdexcept> namespace chat { template <typename ConnFactory> class ConnectionPool : public std::enable_shared_from_this<ConnectionPool<ConnFactory> >, chat::Uncopyable { public: using SPtr = std::shared_ptr<ConnectionPool<ConnFactory> >; using WPtr = std::weak_ptr<ConnectionPool<ConnFactory> >; using ConnType = typename ConnFactory::conn_type; // 需要使用std::function来对deleter进行类型擦除,以便于支持默认删除器和自定义删除器 using PoolInerConnection = std::unique_ptr<ConnType, std::function<void(ConnType*)> >; struct PoolInfo { size_t idleConnNum; size_t totalConnNum; size_t activeConnNum; size_t maxConnNum; size_t expandCount; // 扩容次数 size_t waitingCount; // 等待个数, 阻塞在acquire上的个数 }; // 1. 工厂需要使用shared_ptr吗? // 需要,如果连接池后续需要动态扩容,还是要用到工厂的 explicit ConnectionPool(const std::shared_ptr<ConnFactory>& factory, size_t connNum, size_t maxConnNum = 1024): m_closed(false), m_connNum(connNum), m_maxConnNum(maxConnNum), m_activeConnNum(0), m_waitingCount(0), m_expandCount(0), m_factory(factory) { if (connNum == 0) { throw std::invalid_argument("connNum must be greater than 0"); } connNum = std::min(connNum, maxConnNum); try { createConnections(connNum); } catch(const std::exception& e) { closeAll(); throw; } } ~ConnectionPool() { closeAll(); } /** * @brief从连接池获取一个连接 * @returnnullptr - 连接池关闭则返回空指针 */ std::shared_ptr<ConnType> acquire() { // 快速路径 { m_waitingCount.fetch_add(1, std::memory_order_relaxed); std::lock_guard<std::mutex> lock(m_mtx); m_waitingCount.fetch_sub(1, std::memory_order_relaxed); if (m_closed) { return nullptr; } if (m_pool.empty()) { expand(); } // 扩容后不为空则直接取一个连接返回 if (!m_pool.empty()) { return takeOneConnection(); } } // 慢速路径, 连接池为空,需要等待有可用连接 m_waitingCount.fetch_add(1, std::memory_order_relaxed); std::unique_lock<std::mutex> lock(m_mtx); while (!m_closed && m_pool.empty()) { m_cond.wait(lock); } m_waitingCount.fetch_sub(1, std::memory_order_relaxed); if (m_closed) { return nullptr; } return takeOneConnection(); } /// 连接池状态信息 /** * @brief获取当前连接池内连接数(空闲连接数) */ size_t idleConnNum() const { std::lock_guard<std::mutex> lock(m_mtx); return m_pool.size(); } /** * @brief获取连接池总连接数 = 活跃连接数(申请出去的连接) + 空闲连接数 */ size_t totalConnNum() const { std::lock_guard<std::mutex> lock(m_mtx); return m_connNum; } /** * @brief获取活跃连接数(有多少连接申请出去了) */ size_t activeConnNum() const { std::lock_guard<std::mutex> lock(m_mtx); return m_activeConnNum; } PoolInfo poolInfo() const { PoolInfo info; { std::lock_guard<std::mutex> lock(m_mtx); info.idleConnNum = m_pool.size(); info.totalConnNum = m_connNum; info.activeConnNum = m_activeConnNum; info.maxConnNum = m_maxConnNum; info.expandCount = m_expandCount; info.waitingCount = m_waitingCount.load(); } return info; } private: void createConnections(size_t num) { // 不确定createConnection是否会抛出异常 for (size_t i = 0; i < num; ++i) { m_pool.push_back(m_factory->createConnection()); } } void releaseToPool(ConnType* p, const std::function<void(ConnType*)>& deleter) { std::lock_guard<std::mutex> lock(m_mtx); // 防止归还连接数超过最大连接数 if (m_closed || m_pool.size() >= m_maxConnNum) { deleter(p); return; } m_pool.push_back(PoolInerConnection(p, deleter)); m_cond.notify_one(); } std::shared_ptr<ConnType> takeOneConnection() { // no locker, 调用者应该保证连接池不为空 auto conn = std::move(m_pool.front()); m_pool.pop_front(); auto deleter = conn.get_deleter(); ConnType* ptr = conn.release(); addActiveConnNum(); // 裸指针交给自定义析构函数的shared_ptr包装, 再返回,这样连接不使用时 // 自动归还连接池 std::weak_ptr<ConnectionPool> self = this->shared_from_this(); return std::shared_ptr<ConnType>(ptr, [self, deleter](ConnType* p) { // 如果连接池已经析构, 连接由shared_ptr自动释放, 不需归还 auto pool = self.lock(); if (pool) { pool->releaseToPool(p, deleter); pool->subActiveConnNum(); } else { // 如果无法归还,指针需要有unique_ptr的deleter析构 // 因为有可能是自定义的析构器 deleter(p); } }); } void closeAll() { std::list<PoolInerConnection> tmp; { std::lock_guard<std::mutex> lock(m_mtx); m_pool.swap(tmp); m_closed = true; } m_cond.notify_all(); } /** * @brief动态扩容, 无锁,外层调用需要加锁 * @paramnum - 期望增加的连接数 */ void expandNoLock(size_t num) { if (num == 0 || num > m_maxConnNum || m_connNum >= m_maxConnNum) { return; } size_t newConnNum = m_connNum + num; // TODO: 这里允许超过最大连接数? 先采取允许, 但不能超过maxConnNum * 2 // 后续回收的时候会减少到m_maxConnNum newConnNum = std::min(newConnNum, m_maxConnNum * 2); if (newConnNum == m_connNum) { return; } try { size_t deltaNum = newConnNum - m_connNum; createConnections(deltaNum); m_connNum = newConnNum; ++m_expandCount; // 扩容成功,通知等待线程 m_cond.notify_all(); } catch (const std::exception& e) { // 扩容失败, 保持原有状态 } } // 无锁,只能在加锁后调用 void expand() { // 扩容策略, 达到最大连接数前,每次扩容2倍 size_t curConnNum = m_connNum == 0 ? 1 : m_connNum; if (m_closed || (curConnNum >= m_maxConnNum)) { return; } size_t nextConnNum = std::min((curConnNum * 2), m_maxConnNum); size_t deltaNum = nextConnNum - curConnNum; expandNoLock(deltaNum); } void addActiveConnNum() { ++m_activeConnNum; } void subActiveConnNum() { --m_activeConnNum; } private: mutable std::mutex m_mtx; std::condition_variable m_cond; /// 用于在连接池关闭时通知等待线程退出等待 bool m_closed; /// 当前连接数, 包括空闲连接数和使用中的连接数 size_t m_connNum; /// 最大连接数 size_t m_maxConnNum; /// 记录活跃连接数, 借出去的连接数 size_t m_activeConnNum; /// 记录等待个数 std::atomic<size_t> m_waitingCount; /// 记录扩容次数 size_t m_expandCount; std::shared_ptr<ConnFactory> m_factory; std::list<PoolInerConnection> m_pool; }; /** * @brief基于std::function实现的连接工厂 * @tparamConnType - 连接类型 * @ref case_connection_pool.cpp * @code {.cpp} 使用示例 * 1. 对于unique_ptr<ConnType>, 默认连接deleter * auto factory = std::make_shared<ConnFactory<MockRedisConnection>>( * []() * { * return std::make_unique<MockRedisConnection>(); * }); * * 2. 对于unique_ptr<ConnType, Deleter>, 自定义连接deleter * auto factory = std::make_shared<ConnFactory<MockRedisConnection> >( * [&releasedCount]() * { * // 需要显式使用std::function<void(MockRedisConnection*)>来进行删除器的类型擦除 * auto conn = std::unique_ptr<MockRedisConnection, * std::function<void(MockRedisConnection*)> >( * new MockRedisConnection, * [&releasedCount](MockRedisConnection* p) * { * if (!p) * return; * std::cout << "custom deleter called " << p->id() << std::endl; * delete p; * } * ); * * return conn; * }); * @endcode * */ template <typename ConnType> class ConnFactory { public: using conn_type = ConnType; // 工厂返回的unique_ptr也必须使用std::function进行类型擦除 using GeneratedConnection = std::unique_ptr<conn_type, std::function<void(conn_type*)> >; using Creator = std::function<GeneratedConnection()>; explicit ConnFactory(Creator creator): m_creator(std::move(creator)) {} GeneratedConnection createConnection() { return m_creator(); } private: Creator m_creator; }; // 提供两个连接建立装饰器,分别用于创建默认删除器连接和自定义删除器连接 template <typename ConnType> std::unique_ptr<ConnType, std::function<void(ConnType*)> > makeConnWithDefaultDeleter(std::unique_ptr<ConnType> ptr) { auto deleter = ptr.get_deleter(); return std::unique_ptr<ConnType, std::function<void(ConnType*)> >(ptr.release(), deleter); } template <typename ConnType, typename Deleter> std::unique_ptr<ConnType, std::function<void(ConnType*)> > makeConnWithCustomDeleter(std::unique_ptr<ConnType, Deleter> ptr) { auto deleter = ptr.get_deleter(); return std::unique_ptr<ConnType, std::function<void(ConnType*)> >(ptr.release(), deleter); }
网址:C++连接池练习 https://www.yuejiaxmz.com/news/view/1446296
相关内容
基于C++11的数据库连接池实现如图,在⊙O中,C为 AB的中点,连接AC并延长至D,使CD=CA,连接DB并延
电动车电池连接图
让聪明的车连接智慧的路,C
C#的通用DbHelper类使用数据连接池
数据库 = JDBC,连接池对象
C++练习之 求方程ax²
超时时间已到。超时时间已到,但是尚未从池中获取连接。出现这种情况可能是因为所有池连接均在使用,并且达到了最大池大小。
C语言学习
全连接网络训练中的优化技巧(个人笔记)

