前言

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. JSON-Server 安装

    在后台还没给接口之前,使用JSON-Server搭建一台JSON服务器,将接口要返回的数据放在json文件里面.然后请求这些数据,这样我们可以先做一些东西,等后台接口好了之后直接替换就可以了,不必一直 ...

  2. Struts 2再曝远程代码执行漏洞S2-037

    导读今年4月份,Apache Stuts 2之上发现的S2-033远程代码执行漏洞,以迅雷不及掩耳之势席卷而来.其利用代码很快就在短时间内迅速传播.而且官方针对这个高危漏洞的修复方案还是无效的. 悲剧 ...

  3. 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. ...

  4. 利用CSS3制作淡入淡出动画效果

    CSS3新增动画属性“@-webkit-keyframes”,从字面就可以看出其含义——关键帧,这与Flash中的含义一致. 利用CSS3制作动画效果其原理与Flash一样,我们需要定义关键帧处的状态 ...

  5. Struts2的表单标签还可以为集合中的对象赋值

    •Struts 还允许填充 Collection 里的对象, 这常见于需要快速录入批量数据的场合   代码如下 : TestCollectionAction.java package com.atgu ...

  6. 在虚拟机中的搭建Web服务器(CentOS)

    1.制作本地yum源 相关可查看:http://www.cnblogs.com/xiaomingzaixian/p/8424290.html 2.安装JDK 上传上传jdk-7u45-linux-x6 ...

  7. 实现一个自动生成小学四则运算题目的命令行程序(java实现)

    Github项目地址:https://github.com/xiaobaot/wordcount/tree/master/sizeyusuan 团队成员:谢家明(代码生成)    谢竣(测试完善) 项 ...

  8. [译]关于JavaScript 作用域你想知道的一切

    原文连接 在学习js的过程对闭包什么的,理解不好,偶然搜到这篇文章.豁然开朗,随翻译. Javacript 中有一系列作用域的概念.对于新的JS的开发人员无法理解这些概念,甚至一些经验丰富的开发者也未 ...

  9. CListCtrl控件使用方法总结

    今天第一次用CListCtrl控件,遇到不少问题,查了许多资料,现将用到的一些东西总结如下: 以下未经说明,listctrl默认view 风格为report 相关类及处理函数 MFC:CListCtr ...

  10. Nginx 之 内存池

    1.基本结构 先来学习一下nginx内存池的几个主要数据结构:[见:./src/core/ngx_palloc.h/.c]     ngx_pool_data_t(内存池数据块结构) 1: typed ...