前言

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框架都是用了线程池来处理事务,比如说Thrifteasyrpc等等,接下来我们将使用C++11来实现一个通用的半同步半异步线程池(个人博客也发表了《使用C++11实现一个半同步半异步线程池》)。

实现

一个半同步半异步线程池分为三层。

  1. 同步服务层:它处理来自上层的任务请求,上层的请求可能是并发的,这些请求不是马上就会被处理的,而是将这些任务放到一个同步排队层中,等待处理。
  2. 同步排队层: 来自上层的任务请求都会加到排队层中等待处理,排队层实际就是一个std::queue。
  3. 异步服务层: 这一层中会有多个线程同时处理排队层中的任务,异步服务层从同步排队层中取出任务并行的处理。

这三个层次之间需要使用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实现一个半同步半异步线程池的更多相关文章

  1. 使用C++11 开发一个半同步半异步线程池

    摘自:<深入应用C++11>第九章 实际中,主要有两种方法处理大量的并发任务,一种是一个请求由系统产生一个相应的处理请求的线程(一对一) 另外一种是系统预先生成一些用于处理请求的进程,当请 ...

  2. c++11 实现半同步半异步线程池

    感受: 随着深入学习,现代c++给我带来越来越多的惊喜- c++真的变强大了. 半同步半异步线程池: 事实上非常好理解.分为三层 同步层:通过IO复用或者其它多线程多进程等不断的将待处理事件加入到队列 ...

  3. 分布式缓存系统 Memcached 半同步/半异步模式

    在前面工作线程初始化的分析中讲到Memcached采用典型的Master_Worker模式,也即半同步/半异步的高效网络并发模式.其中主线程(异步线程)负责接收客户端连接,然后分发给工作线程,具体由工 ...

  4. 领导者/追随者(Leader/Followers)模型和半同步/半异步(half-sync/half-async)模型都是常用的客户-服务器编程模型

    领导者-追随者(Leader/Followers)模型的比喻 半同步/半异步模型和领导者/追随者模型的区别: 半同步/半异步模型拥有一个显式的待处理事件队列,而领导者-追随者模型没有一个显式的队列(很 ...

  5. 半同步半异步模式的实现 - MSMQ实现

    半同步半异步模式的实现 - MSMQ实现 所谓半同步半异步是指,在某个方法调用中,有些代码行是同步执行方式,有些代码行是异步执行方式,下面我们来举个例子,还是以经典的PlaceOrder来说,哈哈. ...

  6. (原创)C++半同步半异步线程池2

    (原创)C++半同步半异步线程池 c++11 boost技术交流群:296561497,欢迎大家来交流技术. 线程池可以高效的处理任务,线程池中开启多个线程,等待同步队列中的任务到来,任务到来多个线程 ...

  7. 【Networking】(转)一个非常好的epoll+线程池服务器Demo

    (转)一个非常好的epoll+线程池服务器Demo Reply 转载自:http://zhangyafeikimi.javaeye.com/blog/285193 [cpp] /** 张亚霏修改 文件 ...

  8. Half Sync And Half Async 半同步半异步模式

    如题,这是一个典型的CS结构的模式,至少曾经在CS结构中用过,只是没用好.当年用UDP死活都处理不过来网络命令,用此模式轻松解决. 此模式是典型的三层架构,同步层在一个线程内处理业务逻辑:异步层接受并 ...

  9. 一个ajax同步与异步引发的血案。

    前言 公司做网上促销活动,需要充值换取相应的抽奖资格,抽奖可以获得丰厚的礼品,而且抽奖资格门槛有点高,领导下达命令保证活动的正常上线与运行,领导很重视,就这样,在领导的安排下进行了相关活动的codin ...

随机推荐

  1. Oracle的归档日志

    归档模式的特点和要求 在归档模式下,当LGWR后台进程的写操作从一个重做日志组切换到另一个重做日志组后,归档写后台进程(ARCH/ARCRn)就会将原来的重做日志的信息复制到归档日志文件中. 可以把归 ...

  2. 应用开发之WinForm环境

    本章简言 上一章笔者讲到关于IO文件操作类,了解如何处理文件流.从这一章开始笔者将讲解相对比较高级的知识点.而本章笔者就对WinForm开发的知识点进行讲解和引导.现在很多业务都是面向于B/S模式的开 ...

  3. 探讨Java I/O类和接口

    (输出)Output:程序---->数据源(如某个文件) (输入)Input:数据源---->程序 Java.io定义的I/O类如下表所示: BufferedInputStream Buf ...

  4. HDFS 手写mapreduce单词计数框架

    一.数据处理类 package com.css.hdfs; import java.io.BufferedReader; import java.io.IOException; import java ...

  5. Linux上安装rz和sz命令

    简介 lrzsz 官网入口:http://freecode.com/projects/lrzsz/ lrzsz是一个unix通信套件提供的X,Y,和ZModem文件传输协议 windows 需要向ce ...

  6. Flask之视图函数

    视图示例 @app.route('/hello') def hello(): return 'Hello World' if __name__ == '__main__': app.run() 特殊的 ...

  7. Linux命令(基础1)

    一  命令的基本构成 (PS:Linux发行版本命令大概有200多个,熟练掌握个百八的就行了,其余的有个大概了解) 命令体 选项 参数(对象) ls -l /var 1.1参数:文件 文件类型: d ...

  8. Python之配置文件读写

    ConfigParser模块 一.创建配置文件 在D盘建立一个配置文件,名字为:test.ini 内容如下: [baseconf] host=127.0.0.1 port=3306 user=root ...

  9. Zabbix基本功能使用手册

    Zabbix基本功能使用手册 vim /etc/zabbix/zabbix_agentd.conf 编辑agent配置文件. 指定那些服务器可以来获取数据,可用逗号隔开指定多台服务器. 这个参数表示a ...

  10. Sails 框架学习资料

    一介布衣 http://yijiebuyi.com/so.html?k=sails sails modules 模型自带的方法介绍 2016-09-06  929  nodejs查看更多 node.j ...