使用C++11实现一个半同步半异步线程池
前言
C++11之前我们使用线程需要系统提供API、posix线程库或者使用boost提供的线程库,C++11后就加入了跨平台的线程类std::thread,线程同步相关类std::mutex、std::lock_guard、std::condition_variable、std::atomic以及异步操作相关类std::async、std::future、std::promise等等,这使得我们编写跨平台的多线程程序变得容易,线程的一个高级应用就是线程池,使用线程池可以充分利用多核CPU的并行计算能力,以及避免了使用单个线程的创建和销毁的开销,所以线程池在实际项目中用的很广泛,很多RPC框架都是用了线程池来处理事务,比如说Thrift,easyrpc等等,接下来我们将使用C++11来实现一个通用的半同步半异步线程池(个人博客也发表了《使用C++11实现一个半同步半异步线程池》)。
实现
一个半同步半异步线程池分为三层。
- 同步服务层:它处理来自上层的任务请求,上层的请求可能是并发的,这些请求不是马上就会被处理的,而是将这些任务放到一个同步排队层中,等待处理。
- 同步排队层: 来自上层的任务请求都会加到排队层中等待处理,排队层实际就是一个std::queue。
- 异步服务层: 这一层中会有多个线程同时处理排队层中的任务,异步服务层从同步排队层中取出任务并行的处理。
这三个层次之间需要使用std::mutex、std::condition_variable来进行事件同步,线程池的实现代码如下。
#ifndef _THREADPOOL_H
#define _THREADPOOL_H
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <memory>
#include <functional>
#include <condition_variable>
#include <atomic>
#include <type_traits>
static const std::size_t max_task_quque_size = 100000;
static const std::size_t max_thread_size = 30;
class thread_pool
{
public:
using work_thread_ptr = std::shared_ptr<std::thread>;
using task_t = std::function<void()>;
explicit thread_pool() : _is_stop_threadpool(false) {}
~thread_pool()
{
stop();
}
void init_thread_num(std::size_t num)
{
if (num <= 0 || num > max_thread_size)
{
std::string str = "Number of threads in the range of 1 to " + std::to_string(max_thread_size);
throw std::invalid_argument(str);
}
for (std::size_t i = 0; i < num; ++i)
{
work_thread_ptr t = std::make_shared<std::thread>(std::bind(&thread_pool::run_task, this));
_thread_vec.emplace_back(t);
}
}
// 支持普通全局函数、静态函数、以及lambda表达式
template<typename Function, typename... Args>
void add_task(const Function& func, Args... args)
{
if (!_is_stop_threadpool)
{
// 用lambda表达式来保存函数地址和参数
task_t task = [&func, args...]{ return func(args...); };
add_task_impl(task);
}
}
// 支持函数对象(仿函数)
template<typename Function, typename... Args>
typename std::enable_if<std::is_class<Function>::value>::type add_task(Function& func, Args... args)
{
if (!_is_stop_threadpool)
{
task_t task = [&func, args...]{ return func(args...); };
add_task_impl(task);
}
}
// 支持类成员函数
template<typename Function, typename Self, typename... Args>
void add_task(const Function& func, Self* self, Args... args)
{
if (!_is_stop_threadpool)
{
task_t task = [&func, &self, args...]{ return (*self.*func)(args...); };
add_task_impl(task);
}
}
void stop()
{
// 保证terminate_all函数只被调用一次
std::call_once(_call_flag, [this]{ terminate_all(); });
}
private:
void add_task_impl(const task_t& task)
{
{
// 任务队列满了将等待线程池消费任务队列
std::unique_lock<std::mutex> locker(_task_queue_mutex);
while (_task_queue.size() == max_task_quque_size && !_is_stop_threadpool)
{
_task_put.wait(locker);
}
_task_queue.emplace(std::move(task));
}
// 向任务队列插入了一个任务并提示线程池可以来取任务了
_task_get.notify_one();
}
void terminate_all()
{
_is_stop_threadpool = true;
_task_get.notify_all();
for (auto& iter : _thread_vec)
{
if (iter != nullptr)
{
if (iter->joinable())
{
iter->join();
}
}
}
_thread_vec.clear();
clean_task_queue();
}
void run_task()
{
// 线程池循环取任务
while (true)
{
task_t task = nullptr;
{
// 任务队列为空将等待
std::unique_lock<std::mutex> locker(_task_queue_mutex);
while (_task_queue.empty() && !_is_stop_threadpool)
{
_task_get.wait(locker);
}
if (_is_stop_threadpool)
{
break;
}
if (!_task_queue.empty())
{
task = std::move(_task_queue.front());
_task_queue.pop();
}
}
if (task != nullptr)
{
// 执行任务,并通知同步服务层可以向队列放任务了
task();
_task_put.notify_one();
}
}
}
void clean_task_queue()
{
std::lock_guard<std::mutex> locker(_task_queue_mutex);
while (!_task_queue.empty())
{
_task_queue.pop();
}
}
private:
std::vector<work_thread_ptr> _thread_vec;
std::condition_variable _task_put;
std::condition_variable _task_get;
std::mutex _task_queue_mutex;
std::queue<task_t> _task_queue;
std::atomic<bool> _is_stop_threadpool;
std::once_flag _call_flag;
};
#endif
测试代码
#include <iostream>
#include <string>
#include <chrono>
#include "thread_pool.hpp"
void test_task(const std::string& str)
{
std::cout << "Current thread id: " << std::this_thread::get_id() << ", str: " << str << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
class Test
{
public:
void print(const std::string& str, int i)
{
std::cout << "Test: " << str << ", i: " << i << std::endl;
}
};
class Test2
{
public:
void operator()(const std::string& str, int i)
{
std::cout << "Test2: " << str << ", i: " << i << std::endl;
}
};
int main()
{
Test t;
Test2 t2;
thread_pool pool;
// 启动10个线程
pool.init_thread_num(10);
std::string str = "Hello world";
for (int i = 0; i < 1000; ++i)
{
// 支持lambda表达式
pool.add_task([]{ std::cout << "Hello ThreadPool" << std::endl; });
// 支持全局函数
pool.add_task(test_task, str);
// 支持函数对象
pool.add_task(t2, str, i);
// 支持类成员函数
pool.add_task(&Test::print, &t, str, i);
}
std::cin.get();
std::cout << "##############END###################" << std::endl;
return 0;
}
测试程序启动了十个线程并调用add_task函数加入了4000个任务,add_task支持普通全局函数、静态函数、类成员函数、函数对象(仿函数)以及lambda表达式,并且支持函数传入,该线程池的实现以及测试代码我已经放到了github上。
参考资料
《深入应用C++11--代码优化与工程级应用》
使用C++11实现一个半同步半异步线程池的更多相关文章
- 使用C++11 开发一个半同步半异步线程池
摘自:<深入应用C++11>第九章 实际中,主要有两种方法处理大量的并发任务,一种是一个请求由系统产生一个相应的处理请求的线程(一对一) 另外一种是系统预先生成一些用于处理请求的进程,当请 ...
- c++11 实现半同步半异步线程池
感受: 随着深入学习,现代c++给我带来越来越多的惊喜- c++真的变强大了. 半同步半异步线程池: 事实上非常好理解.分为三层 同步层:通过IO复用或者其它多线程多进程等不断的将待处理事件加入到队列 ...
- 分布式缓存系统 Memcached 半同步/半异步模式
在前面工作线程初始化的分析中讲到Memcached采用典型的Master_Worker模式,也即半同步/半异步的高效网络并发模式.其中主线程(异步线程)负责接收客户端连接,然后分发给工作线程,具体由工 ...
- 领导者/追随者(Leader/Followers)模型和半同步/半异步(half-sync/half-async)模型都是常用的客户-服务器编程模型
领导者-追随者(Leader/Followers)模型的比喻 半同步/半异步模型和领导者/追随者模型的区别: 半同步/半异步模型拥有一个显式的待处理事件队列,而领导者-追随者模型没有一个显式的队列(很 ...
- 半同步半异步模式的实现 - MSMQ实现
半同步半异步模式的实现 - MSMQ实现 所谓半同步半异步是指,在某个方法调用中,有些代码行是同步执行方式,有些代码行是异步执行方式,下面我们来举个例子,还是以经典的PlaceOrder来说,哈哈. ...
- (原创)C++半同步半异步线程池2
(原创)C++半同步半异步线程池 c++11 boost技术交流群:296561497,欢迎大家来交流技术. 线程池可以高效的处理任务,线程池中开启多个线程,等待同步队列中的任务到来,任务到来多个线程 ...
- 【Networking】(转)一个非常好的epoll+线程池服务器Demo
(转)一个非常好的epoll+线程池服务器Demo Reply 转载自:http://zhangyafeikimi.javaeye.com/blog/285193 [cpp] /** 张亚霏修改 文件 ...
- Half Sync And Half Async 半同步半异步模式
如题,这是一个典型的CS结构的模式,至少曾经在CS结构中用过,只是没用好.当年用UDP死活都处理不过来网络命令,用此模式轻松解决. 此模式是典型的三层架构,同步层在一个线程内处理业务逻辑:异步层接受并 ...
- 一个ajax同步与异步引发的血案。
前言 公司做网上促销活动,需要充值换取相应的抽奖资格,抽奖可以获得丰厚的礼品,而且抽奖资格门槛有点高,领导下达命令保证活动的正常上线与运行,领导很重视,就这样,在领导的安排下进行了相关活动的codin ...
随机推荐
- JSON-Server 安装
在后台还没给接口之前,使用JSON-Server搭建一台JSON服务器,将接口要返回的数据放在json文件里面.然后请求这些数据,这样我们可以先做一些东西,等后台接口好了之后直接替换就可以了,不必一直 ...
- Struts 2再曝远程代码执行漏洞S2-037
导读今年4月份,Apache Stuts 2之上发现的S2-033远程代码执行漏洞,以迅雷不及掩耳之势席卷而来.其利用代码很快就在短时间内迅速传播.而且官方针对这个高危漏洞的修复方案还是无效的. 悲剧 ...
- Leetcode-Bianry Tree Maximum Path Sum
Given a binary tree, find the maximum path sum. The path may start and end at any node in the tree. ...
- 利用CSS3制作淡入淡出动画效果
CSS3新增动画属性“@-webkit-keyframes”,从字面就可以看出其含义——关键帧,这与Flash中的含义一致. 利用CSS3制作动画效果其原理与Flash一样,我们需要定义关键帧处的状态 ...
- Struts2的表单标签还可以为集合中的对象赋值
•Struts 还允许填充 Collection 里的对象, 这常见于需要快速录入批量数据的场合 代码如下 : TestCollectionAction.java package com.atgu ...
- 在虚拟机中的搭建Web服务器(CentOS)
1.制作本地yum源 相关可查看:http://www.cnblogs.com/xiaomingzaixian/p/8424290.html 2.安装JDK 上传上传jdk-7u45-linux-x6 ...
- 实现一个自动生成小学四则运算题目的命令行程序(java实现)
Github项目地址:https://github.com/xiaobaot/wordcount/tree/master/sizeyusuan 团队成员:谢家明(代码生成) 谢竣(测试完善) 项 ...
- [译]关于JavaScript 作用域你想知道的一切
原文连接 在学习js的过程对闭包什么的,理解不好,偶然搜到这篇文章.豁然开朗,随翻译. Javacript 中有一系列作用域的概念.对于新的JS的开发人员无法理解这些概念,甚至一些经验丰富的开发者也未 ...
- CListCtrl控件使用方法总结
今天第一次用CListCtrl控件,遇到不少问题,查了许多资料,现将用到的一些东西总结如下: 以下未经说明,listctrl默认view 风格为report 相关类及处理函数 MFC:CListCtr ...
- Nginx 之 内存池
1.基本结构 先来学习一下nginx内存池的几个主要数据结构:[见:./src/core/ngx_palloc.h/.c] ngx_pool_data_t(内存池数据块结构) 1: typed ...