C++ 多线程:实现一个功能完整的线程池

news2024/9/21 22:32:53

C++ 多线程(四):实现一个功能完整的线程池

        今天我们来聊一聊异步编程的知识。在分布式系统中,一个功能完整的线程池类是一切代码的前提。

一个『合格』的线程池该具备哪些功能?

首先,很自然地想到『线程池类里该有个线程对象的集合,然后可以初始化线程对象的个数、创建线程对象、及启动线程主函数』。没错,这些是基本功能,但是,它更重要的功能是『这些线程对象该运行哪些任务,以及怎么运行这些任务』

解决方案就是任务队列,这个队列里装载了各类各样的任务(函数对象、函数指针、仿函数等)。接着,每个线程去循环遍历这个任务队队列,然后执行该任务。所以,这个类的成员组成大致就清晰了:

  • 一个线程对象队列及初始化方法
  • 一个任务队列及任务添加的方法

功能完整的线程池类实现

#include <iostream>
#include <cstring>
#include <stdexcept>
#include <vector>
#include <map>
#include <set>
#include <algorithm>
#include <mutex>
#include <deque>
#include <thread>
#include <pthread.h>
#include <unistd.h>
#include <memory>
#include <condition_variable>
#include <future>
#include <list>

// 线程安全队列
template <typename T>
class ThreadSafeQue {
public:
    ThreadSafeQue() {};
    ~ThreadSafeQue() {};
    bool empty()
    {
        std::unique_lock<std::mutex> mlock(mutex_);
        return queue_.empty();
    }

    bool pop(T&& pData)
    {
        std::unique_lock<std::mutex> mlock(mutex_);
        if (queue_.empty()) {
            return false;
        }
        pData = std::move(queue_.front());
        queue_.pop_front();
        return true;
    }
    void push(T&& item)
    {
        std::unique_lock<std::mutex> mlock(mutex_);
        queue_.push_back(std::move(item));
        mlock.unlock();    
        cond_.notify_all(); 
    }

private:
    std::deque<T> queue_;
    std::mutex mutex_;
    std::condition_variable cond_;
};

// 线程池类
template<class T>
class ThreadPool {
public:
    ThreadPool(uint32_t i, uint32_t j) : _thread_num(i), _task_num(j) {}

    ~ThreadPool() {}

    void Run() {
        for (std::thread &t : _threads) {
            t.join();  // 等待子线程执行结束
        }
    }

    void Init() {
        for (int i = _thread_num; i > 0; --i) {
            _threads.emplace_back([this, i]() {  // 线程对象创建的同时就开始执行
                std::packaged_task<T()> task;
                while(task_queue.pop(std::move(task))) {
                    std::unique_lock<std::mutex> ul(mtx);
                    std::cout << "curr thread id: " << i << "\n";
                    task();
                }
            });
        }
    }

    template <class Callable, class... Args>
    void AddTask(Callable &&func, Args &&... args) {
        std::packaged_task<T()> task(std::bind(std::forward<Callable>(func), std::forward<Args>(args)...));
        ret_status.push_back(task.get_future());
        task_queue.push(std::move(task));
    }

public:
    uint32_t _thread_num;
    uint32_t _task_num;
    ThreadSafeQue<std::packaged_task<T()>> task_queue;  // 线程任务队列
    std::vector<std::thread> _threads;  // 线程对象池
    std::mutex mtx;
    std::vector<std::future<T>> ret_status;
};

class Func {
    public:
    void operator()(int k) {
        std::cout << k << "\n";
        usleep(1000);
    }
};

template <class T>
void FillTaskQue(ThreadPool<T>* tp) {
    for (int i = 0; i < tp->_task_num; i++) {
        Func func;
        tp->AddTask(func, i);
    }
}

int main()
{
    using T = void;
    ThreadPool<T> threadPool(4, 10);
    std::future<T> ret = std::async(FillTaskQue<T>, &threadPool);  
    //ret.wait();  
    threadPool.Init();
    threadPool.Run();
    return 0;
}

上述代码中,首先实现了一个简单的线程安全队列 ThreadSafeQue,用来存放任务。

线程池类 ThreadPool 中定义了 AddTask 模板方法用来添加线程任务(也就是函数对象 Func func)。ret_status 记录了任务执行的完成情况。

上述实现代码量虽然不多,但涉及的知识点还是挺多的:

  • std::async 启动一个任务队列填充的线程
  • std::forward 通常是用于完美转发的,它会将输入的参数原封不动地传递到下一个函数中,
    这个“原封不动”指的是,如果输入的参数是左值,那么传递给下一个函数的参数的也是左值;如果输入的参数是右值,那么传递给下一个函数的参数的也是右值
  • std::packaged_task 包装一个可调用的对象,并且允许异步获取该可调用对象产生的结果,从包装可调用对象意义上来讲,std::packaged_task 与 std::function 类似,只不过 std::packaged_task 将其包装的可调用对象的执行结果传递给一个 std::future 对象(该对象通常在另外一个线程中获取 std::packaged_task 任务的执行结果)。
  • 函数的返回结果用 std::future 接收,可以调用 future.get 等待异步任务执行完成,调用 future.wait 等待异步任务开始执行,future.wait_for 超时等待函数返回结果,但是它的模板类型只能是函数的返回值类型,局限性比较大,可以结合 std::promise 使用,更加灵活。std::promise 也是一个模板类,它的作用是在不同的线程中实现数据的同步,set_value()则直接将 future 的状态设置为ready,使用方法如下:
auto promise = std::,make_shared<std::promise<int32_t>>();
promise->set_value(value)
std::future<int> fut = promise->get_future();

最后,我们再来看下线程池类的测试结果吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/509075.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

被嫌弃可视化太丑?这种可视化大屏搭建方法,分分钟让老板满意

在数据可视化中&#xff0c;使用频率最高的展览方式一定是地图可视化。基本上现有的大屏都是以地图作为主视图来呈现的&#xff0c;没有一幅地图放到大屏中央&#xff0c;已经不好意思给同行说明自己企业数据分析有多牛了。在地图可视化中&#xff0c;最炫酷的一定是3D可视化大…

家用洗地机有什么推荐的吗?家用洗地机哪款好

洗地机是创新、高效的清洁工具&#xff0c;其具有高性能的清洁能力和卓越的操作体验。与传统的清洁工具相比&#xff0c;洗地机可以迅速而彻底地打扫地面&#xff0c;降低清洁时间和人力成本&#xff0c;让我们在工作之余不用花费大量的时间和精力去打扫卫生&#xff0c;下面就…

TCP 协议和 UDP 协议 的优势和劣势

TCP 最核心的价值是提供了可靠性&#xff0c;而 UDP 最核心的价值是灵活&#xff0c;你几乎可以用它来做任何事情。例如&#xff1a;HTTP 协议 1.1 和 2.0 都基于 TCP&#xff0c;而到了 HTTP 3.0 就开始用 UDP 了。UDP 在数据传输、网络控制、音视频、Web 技术中&#xff0c;都…

Chroma向量数据库

嵌入向量&#xff08;vector embedding&#xff09;是表示任何类型数据的 A.I 原生方式&#xff0c;使它们非常适合与各种 A.I 驱动的工具和算法一起使用。 它们可以表示文本、图像&#xff0c;很快还可以表示音频和视频。 有许多创建嵌入的选项&#xff0c;无论是在本地使用已…

Fiddler 微信小程序抓图教程(傻瓜式|汉化版|狗看了直呼内行)

前言 本篇文章主要给大家详细讲解如何用Fiddler爬取微信小程序的图片&#xff0c;内容图文并茂&#xff0c;流程非常简单&#xff0c;我们开始吧。 目录 获取软件并打开点击工具设置相关代理如何抓图答疑总结 一、获取软件并打开 1、通过百度网盘下载获取安装包(链接是永久的…

深度学习学习路线:从入门到精通

深度学习是机器学习的一个分支&#xff0c;已经成为人工智能领域最热门的技术之一。想要深入学习深度学习&#xff0c;需要具备一定的数学和编程基础。以下是一个深度学习学习路线的建议&#xff0c;可以帮助你快速入门深度学习&#xff1a; 数学基础&#xff1a; 线性代数&am…

从模式识别到图像文档分析——浅析场景文本识别研究

目录 一、场景文本识别工作简述二、基于视觉关系预测复杂场景文本识别2.1、FPN骨干网络2.2、文本片段检测模块2.3、候选关系对构建模块2.4、基于关系网络的连接关系预测2.5、损失函数 三、文档图像智能分析与处理前沿研究 文字作为人类语言的书面形式&#xff0c;是文本图像中最…

APP外包项目的线上维护方案

APP的使用已经非常普及&#xff0c;不论是2C还是2B的APP都已经渗透到了我们生活的方方面面&#xff0c;对于APP的开发公司来说APP项目的线上维护是一个非常重要的问题。如果APP项目比较重要而且用户规模比较大&#xff0c;那更需要专业的技术团队来维护。今天和大家分享这方面的…

神经注释精细化:用于肾上腺分析的新3D数据集的开发

文章目录 Neural Annotation Refinement:Development of a New 3D Dataset for Adrenal Gland Analysis摘要本文方法Neural Annotation Refinement 实验结果 Neural Annotation Refinement:Development of a New 3D Dataset for Adrenal Gland Analysis 摘要 人工注释是不完美…

如何将两张图片合成一张,多方式提高10倍效率

如何将两张图片合成一张&#xff1f;在日常工作和生活过程中&#xff0c;图片无处不在。图片能够及时记录当下生活&#xff0c;并且容易保存和传播。随着技术手段的创新和发展&#xff0c;图片质量也在不断提高。如何将多张图片进行有效合并&#xff0c;从而便于保存呢&#xf…

庄懂的TA笔记(十四)<特效:流动 + 扰动>

庄懂的TA笔记&#xff08;十四&#xff09;&#xff1c;特效&#xff1a;流动 扰动> 效果展示&#xff1a; 正文&#xff1a; 大纲&#xff1a; 一、增广&#xff1a; 1、排序问题&#xff1a; 造成这个问题的原因是&#xff0c;他在取背景前&#xff0c;小人的胳膊不算是透…

常用的排序算法--JavaScript

1.冒泡排序 这个函数使用了双重循环&#xff0c;第一个循环用于遍历数组中的每个元素&#xff0c;第二个循环用于比较相邻的元素&#xff0c;如果它们的顺序不正确&#xff0c;则交换它们的位置。在每次内部循环之后&#xff0c;最大的元素都会被移到数组的末尾&#xff0c;因此…

MSR015/MSR025低温漂、低功耗电压基准可pin对pin兼容REF015/REF025

MSR015/MSR025 是低温漂、低功耗、高精度 CMOS 电压基准&#xff0c; 具有0.05% 初始精度、低功耗特点。可pin对pin兼容REF015/REF025。该器件的低输出电压迟滞和低长期输出电压漂移特性&#xff0c;进一步提高稳定性和系统可靠性。 此外&#xff0c;器件的小尺寸和低运行电流特…

《一种使用光电容积图和生物特征进行无需校准的非侵入式血压估计方法》阅读笔记

目录 一、论文摘要 二、论文十问 Q1&#xff1a;论文试图解决什么问题&#xff1f; Q2&#xff1a;这是否是一个新的问题&#xff1f; Q3&#xff1a;这篇文章要验证一个什么科学假设&#xff1f; Q4&#xff1a;有哪些相关研究&#xff1f;如何归类&#xff1f;谁是这一…

GB/T25915.1法规基本标准-附 录 B(资料性)等级划分计算

附 录 B(资料性)等级划分计算实例 B.1 示例1 B.1.1 某个洁净室占地面积18m2,规定洁净度级别为动态ISO5级。使用采样流量为28.3L/min的 离散粒子计数器进行分级测试。2个关注粒径分别为:D≥0.3μm 和D≥0.5μm。 查表 A.1,采样点数 NL 为6。 B.1.2 从表1查得ISO5级的粒子浓度…

设计模式梳理

快速回顾 类别名称应用场景例子创建型模式Factory模式共用统一接口AbstactFactory模式共用统一接口Singleton模式只构建一次&#xff0c;每次构建只返回自己Builder模式一步步的进行复杂对象的构建链式构造器&#xff0c;解决复杂对象多个属性可选择性地设置的问题&#xff0c…

【ChatGPT】你会是被AI抢饭碗的那类人吗?

文章目录 前言一、AI替代“基础性工作”&#xff0c;二、AI没有魔法&#xff1a;人类做不到&#xff0c;它也做不到三 人类的恐惧&#xff1a;被替代、被超越四 AI让语言返祖&#xff0c;小语种与文化“濒危灭绝”五 人类的未来&#xff0c;教育何去何从&#xff1f;总结 前言 …

2023/5/9总结

Java基础&#xff08;3&#xff09; 1、成员变量和局部变量的区别 2.private关键字 是一个权限修饰符可以修饰成员变量和成员方法作用是保护成员不被别的类使用&#xff0c;被private修饰的成员只能在本类中才能访问 针对private修饰的成员变量&#xff0c;如果需要被其它类使…

缓存穿透、缓存雪崩和缓存击穿

1 缓存穿透 缓存穿透是指查询一个一定不存在的数据&#xff0c;由于缓存中没有&#xff0c;每次查询都要去数据库中查询&#xff0c;导致频繁地访问数据库&#xff0c;从而影响系统的性能。攻击者可以利用这一点&#xff0c;对系统进行拒绝服务攻击。 1.1 缓存穿透举例 攻击者…

课程《JavaWeb基础框架程序设计》考试题上篇——基础应用题(计算应用、水仙花数)

文章目录 &#x1f4cb;前言&#x1f3af;第一题&#xff08;30分&#xff09;&#x1f3af;第二题&#xff08;30分&#xff09;&#x1f4dd;最后 &#x1f4cb;前言 这篇文章是大学课程《JavaWeb基础框架程序设计》考试题目的内容&#xff0c;包括了原题和答案。题目只包括…