C 线程池一直都是各位程序员们造轮子的首选项目之一。今天,小编带大家一起来看看这个轻量的线程池,本线程池是header-only的,并且整个文件只有100行,其中C 的高级用法有很多,很值得我们学习,一起来看看吧。

以下是正文


线程池

C 带有线程操作,异步操作,就是没有线程池,至于线程池的概念,我先搜一下别人的解释:

一般而言,线程池有以下几个部分:

1. 完成主要任务的一个或多个线程。

2. 用于调度管理的管理线程。

3. 要求执行的任务队列。

我来讲讲人话:你的函数需要在多线程中运行,但是你又不能每来一个函数就开启一个线程,所以你就需要固定的N个线程来跑执行,但是有的线程还没有执行完,有的又在空闲,如何分配任务呢,你就需要封装一个线程池来完成这些操作,有了线程池这层封装,你就只需要告诉它开启几个线程,然后直接塞任务就行了,然后通过一定的机制获取执行结果。

这里有一个100行实现线程池的操作:

https://github.com/progschj/ThreadPool/blob/master/ThreadPool.h

分析源代码 头文件

#include

#include

#include

#include

#include

#include

#include

#include

#include

vector,queue,momory 都没啥说的,thread线程相关,mutex 互斥量,解决资源抢占问题,condition_variable 条件量,用于唤醒线程和阻塞线程,future 从使用的角度出发,它是一个获取线程数据的函数。functional 函数子,可以理解为规范化的函数指针。stdexcept 就跟它的名字一样,标准异常。

class ThreadPool {

public:

ThreadPool(size_t);

template

auto enqueue(F&& f, Args&&... args)

-> std::future::type>;

~ThreadPool();

private:

// need to keep track of threads so we can join them

std::vector< std::thread > workers;

// the task queue

std::queue< std::function> tasks;

// synchronization

std::mutex queue_mutex;

std::condition_variable condition;

bool stop;

};

线程池的声明,构造函数,一个enqueue模板函数 返回std::future, 然后这个type又利用了运行时检测(还是编译时检测?)推断出来的,非常的amazing啊。成功的使用一行代码反复套娃,这高阶的用法就是大佬的水平吗,i了i了。

workers 是vector<:thread>俗称工作线程。

std::queue<std::function> tasks 俗称任务队列。

那么问题来了,这个任务队列的任务只能是void() 类型的吗?感觉没那么简单,还得接着看呐。

mutex,condition_variable 没啥讲的,stop 控制线程池停止的。

// the constructor just launches some amount of workers

inline ThreadPool::ThreadPool(size_t threads)

:   stop(false)

{

for(size_t i = 0;i

workers.emplace_back(

[this]

{

for(;;)

{

std::functiontask;

{

std::unique_lock<:mutex>lock(this->queue_mutex);

this->condition.wait(lock,

[this]{ return this->stop || !this->tasks.empty(); });

if(this->stop && this->tasks.empty())

return;

task = std::move(this->tasks.front());

this->tasks.pop();

}

task();

}

}

);

}

大佬写的注释就是这么朴实无华,说这个构造函数仅仅是把一定数量的线程塞进去,我是看了又看才悟出来这玩意是什么意思……虽然本质上的确是它说的只是把线程塞进去,但是这个线程也太绕了。

workers.emplace_back 参数是一个lambda表达式,不会阻塞,也就是说最外层的是一个异步函数,每个线程里面的事情才是重点。

labmda表达式中最外层是一个死循环,至于为什么是for(;;)而不是while(1) 这虽然不是重点,不过大佬的用法还是值得揣摩的,我估计是效率会更高?

task 申明后,紧跟着一个大括号,这个{}里面的部分,是一个同步操作,至于为什么用this->lock 而不是直接使用[&]来捕获参数,想来也是处于内存考虑。精打细算的风格像极了抠门的地主,i了i了。

紧接着一个wait(lock,condtion)的操作,像极了千层饼的套路。

第一层:这TM不是要锁死自己啊?这样不是构造都得卡死?

第二层:我们看到它emplace_back了一个线程,不会阻塞,但是等开锁,锁不就在它自己的线程里面嘛?那不得锁死了啊?

第三层:我们看到这个lock其实只是个包装,真正的锁是外层的mutex,所以从这里是不存在死锁的。但是你的wait的condition怎么可能不懂呢,必须要 stop 或者 !empty 才wait吗?

第四层:我们查资料发现后面的condition是返回false才会wait,也就是说要!stop && empty才会wait,就是说这个线程池是 运行态,并且没有任务才才会执行等待操作!否则就不等了,直接冲!

第五层:既然你判断了上面判断了stop和非空,为啥下面还要判断stop和空才退出呢?不显得冗余?

第六层:要确定它的确是被置为stop了,且队列执行空了,它才能够光荣退休。有没有问题呢,有,最后所有线程都阻塞了,你stop置为true它们也不知道啊……

我估计它的stop会有唤醒所有线程的操作,不过如果有的在执行,有的在等待,应该没办法都通知到位,但是在执行的在下一次判断的时候也能正常退出。

因为有了疑惑,我们就想看stop相关的操作,结果发现放在了析构函数里面……

// the destructor joins all threads

inline ThreadPool::~ThreadPool()

{

{

std::unique_lock<:mutex>lock(queue_mutex);

stop = true;

}

condition.notify_all();

for(std::thread &worker: workers)

worker.join();

}

{}里面上锁进行了stop为true的操作,至于为什么不用原子操作,我也不知道,但是仔细想了下大概是因为本来就有一把锁了,再用原子就不是内味儿了。然后它果然通知了所有,并且还把工作线程join了。也就是等它们结束。

结束了千层饼の解析之后,我们看看最重要的入队操作

// add new work item to the pool

template

auto ThreadPool::enqueue(F&& f, Args&&... args)

-> std::future::type>

{

using return_type = typename std::result_of::type;

auto task = std::make_shared< std::packaged_task>(

std::bind(std::forward(f), std::forward(args)...)

);

std::futureres = task->get_future();

{

std::unique_lock<:mutex>lock(queue_mutex);

// don't allow enqueueing after stopping the pool

if(stop)

throw std::runtime_error("enqueue on stopped ThreadPool");

tasks.emplace([task](){ (*task)(); });

}

condition.notify_one();

return res;

}

typename std::result_of::type中的typename 应该是为消除歧义的,或者因为嵌套依赖名字的关系,做为一个坚决不写模板的普通程序员,这段代码太难了……-> type 我倒是知道怎么回事,就是指明它的返回类型的一种方式result_of应该是指明了F是一个函数,签名为Args...这个变参,Args是啥它不关系,它关心的是返回值的参数类型 所以有个type。

至于为什么函数入口是一个右值引用那就超出我的理解范围了。难道说functional 必须要右值引用?那它的销毁谁来管呢?这个线程来管吗?这些坑我以后慢慢填。

前面我们说了tasks 只能接收void() 的函数类型,这里使用std::packaged_task完成对函数类型的推导,至于为什么不用 function,因为这还不是最终放入tasks的对象,它要承接一个返回future的工作,而package_task就是来打包返回future的……

然后就是加锁入队 通知工作线程 返回future的操作。本来是线程池最难理解的部分,反而显得平淡无奇了,因为前面那些花里胡哨的操作已经很好的打通了我们的理解能力。对于这个操作本来就有一点概念的,就显得有种“就这?”的感觉……

声明:

本文于网络整理,版权归原作者所有,如来源信息有误或侵犯权益,请联系我们删除或授权事宜。

C 11 实现的 100行 线程池相关推荐

  1. C 11实现的100行线程池

    [导读]:C 线程池一直都是各位程序员们造轮子的首选项目之一.今天,小编带大家一起来看看这个轻量的线程池,本线程池是header-only的,并且整个文件只有100行,其中C 的高级用法有很多,很值得 ...

  2. threadpool的数量_多线程之旅(11)_如何限制系统线程池ThreadPool的最大最小并发数量_SetMaxThreads/SetMinThreads用法...

    ThreadPool有两个设置线程池并发数量的方法,分别是: ThreadPool.SetMinThreads(int workerThreads, int completionPortThreads ...

  3. python多线程,多进程,线程池,进程池

    https://blog.csdn.net/somezz/article/details/80963760 python 多线程 线程(Thread)也叫轻量级进程,是操作系统能够进行运算调度的最小单 ...

  4. Android AsyncTask 深度理解、简单封装、任务队列分析、自定义线程池

    前言:由于最近在做SDK的功能,需要设计线程池.看了很多资料不知道从何开始着手,突然发现了AsyncTask有对线程池的封装,so,就拿它开刀,本文将从AsyncTask的基本用法,到简单的封装,再到 ...

  5. java自定义线程池池,线程池使用及自定义线程池

    一 案例引申 编写代码同时只允许五个线程并发访问(以下文的函数为例子) private static void method() { System.out.println("ThreadNa ...

  6. Java多线程:线程池

    一. 背景 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,合理的使用线程池可以对线程进行统一的分配.调优和监控,并有以下好处:     第一:降低资源消耗.通过重复利用已 ...

  7. Android性能优化之使用线程池处理异步任务

    说到线程,我想大家都不陌生,因为在开发时候或多或少都会用到线程,而通常创建线程有两种方式: 1.继承Thread类 2.实现Runnable接口 虽说这两种方式都可以创建出一个线程,不过它们之间还是有 ...

  8. android asynctask 线程池,AsyncTask和线程池

    在Android中,UI操作是线程不安全的,如果想要在子线程中进行UI操作,或者在主线程中进行耗时操作,则需要借助Android的异步消息处理机制.但我们需要开启N个线程的时候,我们可以这样做: 直接 ...

  9. 手写一个简单的线程池MyThreadPool

    说明 手写的一个简单的线程池,旨在帮助了解线程池的工作原理. 核心内容 核心工作线程 任务阻塞队列 定义一个内部类去实现核心工作线程 /*** 内部类:工作的核心线程*/private final c ...

最新文章

  1. 2017年6月16号课堂笔记
  2. 达芬奇SP型机器人二度获准FDA,可用于特定耳鼻喉手术...
  3. 数仓dw怎么建_网易严选如何打造数仓规范和评价体系
  4. C语言之理解(*(void (*)())0)()和signal函数
  5. Kotlin学习笔记22 协程part2 join CoroutineScope 协程vs线程
  6. camunda流程定义表无数据_【经验】数据可视化分析操作指南
  7. python函数的规则_Python函数的作用域规则和闭包
  8. 【优化求解】基于matalb改进的遗传算法GA求解城市交通信号优化问题【含Matlab源码 214期】
  9. Maximum Score After Splitting a String
  10. 计算机网络ip地址计算广播地址,ip地址计算与子网划分的方法讲解
  11. 西安大略大学计算机专业排名,加拿大大学计算机专业排名一览
  12. python3 爬取网页表格例子
  13. [LTE 架构]4G核心网EPC都有什么花花肠子 # 承载、附着、MME\SGW\PGW,EPS
  14. android没有adm_求助 Android studio的ADM打不开是什么原因
  15. 一个能启动电脑的U盘
  16. Linux 容器化技术详解(虚拟化、容器化、Docker)
  17. 信捷服务器Z相信号,信捷plc标记与中断处理小知识
  18. 学java被“劝退”的第七天
  19. 服务端向客户端主动发送消息
  20. 一起来电磁流量计的“世界”看看!

热门文章

  1. jvm分配内存_为JVM分配内存:一个案例研究
  2. junit测试设置不回滚_正确设置JUnit测试名称
  3. 从外部CorDapp扩展和覆盖流
  4. mongodb插入速度每秒_MongoDB事实:商品硬件上每秒插入80000次以上
  5. java nio的演进_Java接口的防御性API演进
  6. Java数据类型和标识符
  7. 您真的需要instanceof吗?
  8. Quasar和Akka –比较
  9. 设置本地Nexus存储库并从Maven部署WAR文件
  10. 休眠身份,序列和表(序列)生成器