模型

源码分析

1)接口

class ThreadPool : noncopyable
{public:typedef std::function<void ()> Task;explicit ThreadPool(const string& nameArg = string("ThreadPool"));~ThreadPool();void setMaxQueueSize(int maxSize) { maxQueueSize_ = maxSize; }  // 设置队列大小void setThreadInitCallback(const Task& cb)                      // 设置回调{ threadInitCallback_ = cb; }void start(int numThreads);   // numThreads为要创建的子线程数目,开启线程void stop();                  // 关闭线程const string& name() const{ return name_; }size_t queueSize() const;void run(Task f);             // 任务执行函数
};

2)成员变量

private:bool isFull() const REQUIRES(mutex_);    void runInThread();Task take();mutable MutexLock mutex_;   //互斥锁Condition notEmpty_ GUARDED_BY(mutex_);  // 条件变量: 非空判断Condition notFull_ GUARDED_BY(mutex_);   // 条件变量: 非满判断string name_;Task threadInitCallback_;                // 回调函数std::vector<std::unique_ptr<muduo::Thread>> threads_;  // 线程组 使用智能指针维护单个线程对象std::deque<Task> queue_ GUARDED_BY(mutex_);  //双向队列 维护要执行的任务size_t maxQueueSize_;bool running_;  //是否运行标志 

3)线程启停

void ThreadPool::start(int numThreads)
{assert(threads_.empty());running_ = true;threads_.reserve(numThreads);for (int i = 0; i < numThreads; ++i){char id[32];snprintf(id, sizeof id, "%d", i+1);threads_.emplace_back(new muduo::Thread(std::bind(&ThreadPool::runInThread, this), name_+id)); //这里使用emplace_back(c++11)是为了提高插入性能,对比push_backthreads_[i]->start();   // 开启numThreads个线程加入线程池,等待有任务时进行调度}if (numThreads == 0 && threadInitCallback_){threadInitCallback_();}
}void ThreadPool::stop()
{{MutexLockGuard lock(mutex_);  // 线程停止时加锁running_ = false;notEmpty_.notifyAll();notFull_.notifyAll();}for (auto& thr : threads_){thr->join();}
}

说明:线程开始时不需要加锁,是因为此时只是创建空线程,并没有具体的任务来进行调度,故为单线程,不存在数据被多线程改变的情况。stop的时候有共享数据的修改,故需要加锁。

4)存任务

//注意: 存任务时一般需要进行队列非满判断; 取任务时一般需要进行队列非空判断
void ThreadPool::run(Task task)
{if (threads_.empty()){task();}else{MutexLockGuard lock(mutex_);while (isFull() && running_)  {notFull_.wait();  }if (!running_) return;assert(!isFull());queue_.push_back(std::move(task));notEmpty_.notify();  //非满通知 注意这里时notify(),而不是notifyAll()}
}

5)取任务

ThreadPool::Task ThreadPool::take()
{MutexLockGuard lock(mutex_);// always use a while-loop, due to spurious wakeupwhile (queue_.empty() && running_){notEmpty_.wait();}Task task;if (!queue_.empty()){task = queue_.front();queue_.pop_front();if (maxQueueSize_ > 0){notFull_.notify();}}return task;
}

这里主要关注一个虚假唤醒的问题:

while (queue_.empty() && running_)
{notEmpty_.wait();
}

关于虚假唤醒,可以查看:muduo网络库——条件变量(condition variable)_却道天凉_好个秋的博客-CSDN博客

多个线程在同时等待queue不为空的条件发生。当有信号signalA通知queue不为空时,操作系统同时唤醒所有线程,如线程A竞争成功,执行下面的pop操作,使得queue再次为空。但其他线程此时也被唤醒,又由于线程A的操作导致queue再次为空,其他线程无法执行对应的操作,即被虚假唤醒。

6)子线程运行函数

void ThreadPool::runInThread()
{try{if (threadInitCallback_){threadInitCallback_();}while (running_)        // 每一个线程都是一个loop 只有在stop的时候才会停止{Task task(take());    // 从队列中取任务 if (task)          {task();             // 如果取到任务,则执行任务内容}}}...
}

测试

#include "muduo/base/ThreadPool.h"
#include "muduo/base/CountDownLatch.h"
#include "muduo/base/CurrentThread.h"
#include "muduo/base/Logging.h"#include <stdio.h>
#include <unistd.h>// 作为入队任务,有任务执行时打印当前线程id
void print()
{printf("tid=%d\n", muduo::CurrentThread::tid());
}void test(int maxSize)
{LOG_WARN << "Test ThreadPool with max queue size = " << maxSize;muduo::ThreadPool pool("MainThreadPool");   // 创建线程池pool.setMaxQueueSize(maxSize);              // 设置队列最大值pool.start(5);                              // 开启5个子线程来进行调度LOG_WARN << "Adding";pool.run(print);  // 入队pool.run(print);  // 入队for (int i = 0; i < 100; ++i){char buf[32];snprintf(buf, sizeof buf, "task %d", i);pool.run(std::bind(printString, std::string(buf)));}LOG_WARN << "Done";muduo::CountDownLatch latch(1);pool.run(std::bind(&muduo::CountDownLatch::countDown, &latch));latch.wait();pool.stop();
}int main()
{test(0);test(1);test(5);test(10);test(50);
}

总结

线程池为避免频繁创建、销毁线程,提供一组子线程,能从工作队列取任务、执行任务,而用户可以向工作队列加入任务,从而完成用户任务。

主要步骤如下:

1)创建一组线程,每个线程为一个loop循环;

2)有任务需要执行时,入队列;

3)当有线程池中有线程空闲时,从队列中取出任务执行回调;

muduo网络库——ThreadPool相关推荐

  1. muduo网络库源码复现笔记(十七):什么都不做的EventLoop

    Muduo网络库简介 muduo 是一个基于 Reactor 模式的现代 C++ 网络库,作者陈硕.它采用非阻塞 IO 模型,基于事件驱动和回调,原生支持多核多线程,适合编写 Linux 服务端多线程 ...

  2. muduo网络库的封装

    一.基础socket编程 网络编程的底层离不开socket,其处理流程表示如下: int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockadd ...

  3. Muduo网络库核心梳理

    Muduo网络库 Muduo网络库本身并不复杂,是一个新手入门C++面向对象网络编程的经典实战项目.但是,新手在刚刚上手读代码的时候,非常容易陷入代码的汪洋大海,迷失方向.本文旨在简要梳理Muduo网 ...

  4. muduo网络库学习总结:基本架构及流程分析

    muduo网络库学习:基本架构及流程分析 基本架构 Basic Reactor Mutiple Reactor + ThreadPool muduo库的基本使用 基本结构介绍 EventLoop类 P ...

  5. muduo网络库学习(1)

    muduo网络库学习(1) 文章目录 muduo网络库学习(1) 前言 一.muduo是什么? 二.代码结构 1.base库 2.net库 3.附属库 二.网络库结构 总结 前言 本章节主要介绍mud ...

  6. muduo网络库之Acceptor、TcpServer

    本篇博客针对Acceptor类和TcpServer类做下小结. 博客代码来自于陈硕的muduo网络库,github地址https://github.com/chenshuo/muduo 学习笔记: A ...

  7. muduo网络库学习(八)事件驱动循环线程池EventLoopThreadPool

    muduo是支持多线程的网络库,在muduo网络库学习(七)用于创建服务器的类TcpServer中也提及了TcpServer中有一个事件驱动循环线程池,线程池中存在大量线程,每个线程运行一个Event ...

  8. muduo网络库学习(七)用于创建服务器的类TcpServer

    目前为止,涉及到的绝大多数操作都没有提及线程,EventLoop,Poller,Channel,Acceptor,TcpConnection,这些对象的执行都是在单独线程完成,并没有设计多线程的创建销 ...

  9. muduo网络库学习(四)事件驱动循环EventLoop

    muduo的设计采用高并发服务器框架中的one loop per thread模式,即一个线程一个事件循环. 这里的loop,其实就是muduo中的EventLoop,所以到目前为止,不管是Polle ...

最新文章

  1. 【Qt】监视文件和目录的修改:QFileSystemWatcher
  2. Unable to instantiate application
  3. 设计模式学习笔记(十六:桥接模式)
  4. linux Hadoop HDFS NameNode进程重启脚本(linux进程通用)
  5. PHP增加$_ENV变量
  6. What happens when clicking interaction record work center?
  7. 欧拉路HDU3018
  8. 现代软件工程课件 需求分析 如何提出靠谱的项目建议 NABCD
  9. (三)springcloud服务提供者实现
  10. 详细解释下头条图文和微头条发布建议
  11. Linux学习之CentOS(十三)--CentOS6.4下Mysql数据库的安装与配置
  12. VMware 修复可窃取管理员凭据的高危漏洞
  13. inDesign教程,如何制作个性化的感谢卡?
  14. 平滑滤波器模板尺寸与平滑效果的关系_冲压模具丨折弯尺寸、毛刺、、卯合、滑块不顺的根源...
  15. 使用opencv的nonfree模块
  16. 【UBB】UBB代码简介
  17. 主数据管理功能模块、实施、提供厂商
  18. HTML网页黑白滤镜
  19. 1.js变量、作⽤域和引⽤类型,js制作页。
  20. linux 纪元时间转换,[转]Linux下时间函数time gettimeofday

热门文章

  1. vcredist_x64.exe 安装报错:0x80070003系统找不到指定的路径
  2. Swift5多线程系列一GCD基础篇
  3. Linux终端文件夹管理员,如何在Ubuntu 18.04中以管理员权限打开文件和文件夹
  4. vivado烧写flash
  5. numpy 学习汇总15 -广播 ( 基础学习 tcy)
  6. selectedIndex的值竟然为-1
  7. 用Python给图片加上序号类的一致水印
  8. linux中vim中swapfile,在vim中撤消从交换文件恢复(Undo recovery from swap file in vim)
  9. 【TypeScript入门】TypeScript入门篇——命名空间
  10. 考上好大学,然后进入IT行业是穷人孩子晋级中产的唯一出路?(好多年不转载别人的文章了,本文的话题是我非常关注的,一直没有写)