原文连接-线程池的自我修养 mysql
最近重构行情服务端的框架,其中有一部分就是重写mysql线程池,线程池是一个很独立的东西,今天就拿出来给你们分享, 怎样设计一个线程池, 以及我是怎么作的.常见的线程池使用场景分为两种程序员
这个很好理解, 当程序须要大量计算, 单核CPU跑到100%, 这个时候能够将计算任务分解, 分多个线程计算, 若是咱们有4核, 那这个时候咱们能够跑到400%, 理想状况下, 能够节省3倍的时间. 固然这个不是绝对的, 具体状况要具体分析. 总而言之, 是为了让程序充分打满CPU.web
若是这个是web程序, 异步绝对是提升并发的神器. 在咱们的C++服务器中, 也会有大量的阻塞任务, 多是读取mysql, 多是读取mongodb, 或者任意须要同步等待完成的事情, 那么在等待的时候, 咱们的工做线程是彻底无法作别的工做的, 这个时候咱们就把等待的过程, 变成一个任务, 让线程池去作, 主线程继续处理别的工做, 等线程池完成以后, 再接管任务, 继续往下面执行.sql
这是两种彻底不一样的工做内容, 看上去都是线程池, 须要注意的细节, 是彻底不同的, 好比开启的线程数量, 大量计算的时候, 咱们开的线程, 尽可能是小于CPU数量的, mysql访问的时候, 线程数必定是不能高于mysql的并发数的. 这种细节不少, 不一样的情景状况不同, 不能一律而就.mongodb
今天我要给你们分享的线程池, 抛开任务的细节, 主要讲咱们应该怎样去设计一个线程池.编程
无论任务多么复杂, 最终都在这个模型上. 重点能够分为下面几个:缓存
每一个点的设计, 不一样的人有不一样的方法, 向你们分享个人方法, 主要针对的是mysql线程池的设计, 仅供你们参考.bash
线程间通讯有不少种方法, 多是信号, 多是管道, 多是套接字, 我比较喜欢更高级的封装zmq. 无论怎样的通讯方式, 咱们须要保证下面两点:服务器
不论是主工做线程与调度线程之间, 仍是调度线程与线程池线程之间, 必定是异步完成, 绝对不容许同步, 任何地方有同步逻辑, 将成为整个线程池的瓶颈.多线程
一个请求, 只能返回一次, 绝对不能一问多答, 更不能只问不答. 线程池要向主工做线程保证, 过来的请求, 必定会返回, 而且有且只有一次返回. 同时我建议, 若是线程池内部发生执行异常, 不要作二次尝试, 直接将异常标记返回.
通讯模块的设计, 要保证简单高效, 给外面暴露的接口简单到只有接收任务和发送结果两个接口, 过多冗余的设计, 只是无畏的增长了复杂度.
先上张图
调度线程须要关注的也是两点:
这部分我也喜欢交给zmq去作, 有任何消息的时候直接回调, 这里我将外部主线程消息与线程池消息都放在一个消息队列, 既符合先进先出的模式, 也符合单线程同步执行的逻辑.
当过来的任务超过线程池真实并发数量的时候, 咱们会将任务缓存在队列, 而后当工做线程执行完任务的时候,或者有新的任务过来的时候, 咱们都会去检查是否有空余的工做线程, 而后将任务分配给工做线程.
将全部的工做抽象成通用的任务, 得益于C++的类型转换, 咱们能够将全部的入参, 和出参都打包成一个void*, 而后将具体执行任务的过程, 使用一个静态函数, 这样打包一个通用的工做任务.
/**
* @brief 给db层发送的参数
*/
struct DBParam
{
DBParam():
m_type(fund_begin),
m_seq(0)
{}
//! 须要执行的sql
std::string m_str_sql;
//! db的类型
db_res_type m_type;
//! 请求的seq
uint64_t m_seq;
};
/**
* @brief 从db返回的数据
*/
class ResFund:
public ResBase
{
public:
ResFund(){}
//! 基础数据集合
std::vector<FundInfo> m_vec_funds;
};
//! 交给各个服务的正真执行sql的回调函数
typedef void (*DBQueryHandler)(MYSQL* con, void* param, void* res)
class DbMessage:
public MessageBase{
public:
/**
* @brief 构造函数
*/
DbMessage();
/**
* @brief 析构函数
*/
virtual ~DbMessage();
//! 须要执行的参数
void* m_params;
//! 执行以后, 产生的结果信息
void* m_msg;
//! 执行mysql的回调
DBQueryHandler m_handle_fun;
};
复制代码
上面的代码删除了一些敏感的信息, 将主体拿出来, 大体表示我是怎么打包一个任务的. 事实上无论线程池作得多么的好, 业务变幻无穷, 咱们很难知足的, 而咱们这个任务的封装最主要的就是把业务封装到任务里面, 咱们经过一个DBQueryHandler的回调函数, 主工做线程将本身的业务写到回调里面, 交由工做线程完成, 进而实现业务的变幻无穷.
看过大多线程池的实现, 不少人都喜欢用锁, 好比消息队列, 任务队列, 用各类锁来竞争, 进而实现任务的分发, 不敢说这个性能怎么样, 可是一旦扯上锁, 整个代码复杂度就上去了, 一处用锁, 处处加锁. 这个线程池的设计是彻底没有任何锁的, 单线程内部彻底是消息驱动, 线程间消息投递, 简单高效.
线程池的设计见仁见智, 不一样的设计可能基于不一样的需求, 没有银弹. 可是必定要把接口设计得简单, 不要有酷炫吊炸天的功能, 良好的文档, 对使用者友好, 一眼就能看懂的接口, 才是咱们要追求的, 一句话, 简单,简单,再简单.
如您对个人文章感兴趣,请订阅如下公众号, 我将给您讲述, 中小企业程序员, 淘金路上的故事.