线程池小项目【Linux C/C++】(踩坑分享)

news2025/1/31 8:15:57

目录

前提知识:

一,线程池意义

二,实现流程

阶段一,搭建基本框架

1. 利用linux第三方库,将pthread_creat线程接口封装

2. 实现基本主类ThreadPool基本结构 

阶段二,完善多线程安全

1. 日志信息打印——模拟企业级日常日志记录

2. C/C++的格式化输出

3. C,C++接口套用时,考虑this指针

阶段三,优化为单例模式——懒汉

四,源码


嗨!收到一张超美的风景图,愿你每天都能顺心

前提知识:

C/C++线程接口使用,可参考多线程基础入门【Linux之旅】——上篇【线程控制 || 线程互斥 || 线程安全】-CSDN博客

互斥锁,信号量知识,可参考多线程基础入门【Linux之旅】——下篇【死锁 || 条件变量 || 生产消费者模型 || 信号量】-CSDN博客

C++STL接口使用,如:queue,string等

单例模式,懒汉与饿汉模式,可参考C++特殊类设计【特殊类 || 单例对象 || 饿汉模式 || 懒汉模式】-CSDN博客

一,线程池意义

   一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内 存、网络sockets 等的数量。
线程池的应用场景:
   1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB 服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet 连接请求,线程池的优点就不明显了。因为 Telnet 会话时间比线程的创建时间大多了。
  2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
  3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。
线程池示例:
  1. 创建固定数量线程池,循环从任务队列中获取任务对象。
  2. 获取到任务对象后,执行任务对象中的任务接口。

二,实现流程

阶段一,搭建基本框架

 目标:

1. 利用linux第三方库,将pthread_creat线程接口封装

#include <iostream>
#include <pthread.h>
#include <functional>
#include <string>
#include <unistd.h>

typedef void* (*func_t)(void*); // pthread_creat() C接口不接受包装器functional

// 当线程处理函数运行时需要便捷得知当前线程的信息,
// 因此我们将传入一个结构体。
class ThreadDate
{
public:
    void* args;         // 线程任务函数参数
    std::string _name;  // 线程名字
    pthread_t _id;      // 线程ID
    // void* _TP;       // 主进程对象
};

class Thread
{
public:
    Thread(int pid, func_t calltalk, void* args):func_(calltalk)
    {
        char name[64]; 
        snprintf(name, sizeof name, "Thread - %d ", pid);  // 将线程编号导入到名字中

        trda_._name = name; 
        trda_._id = pid;
    }
    
    void start()
    {
        pthread_create(&trda_._id, nullptr, func_ , (void*)&trda_);
    }

    void join()
    {
        pthread_join(trda_._id ,nullptr);
    }

    const std::string& name()
    {
       return trda_._name;
    }

private:
    func_t func_;       // 处理函数
    ThreadDate trda_; // 给任务提供的线程等其他信息
};

当然 我们也可以直接使用C++11提供的thread库,这会简单许多。

2. 实现基本主类ThreadPool基本结构 

template <class T>
class ThreadPool
{
public:
    ThreadPool(int num = THREADPOOL_NUM):_pnum(num)
    {
        for (int i = 0; i < num; i++)
        {
            _thr_pool.push_back(new Thread(i + 1, routine, nullptr));
        }
    }

    // 多线程处理函数——重要函数
    static void* routine(void* trda)  
    // 关于 routine函数,要设置为静态的原因:
    // 在Thread构造中,第二位为void*(*func_t)(void*)类型
    // 而类中成员函数都隐藏了一个this指针,这样会导致routine类型不匹配的问题
    {
        ThreadDate* _trda = static_cast<ThreadDate*>(trda);
        std::cout <<  _trda->_name << std::endl;
        return nullptr;
    }

    // 线程池维护区
    void Run()
    {
        for (auto& e : _thr_pool)
        {
            e->start();
        }
    }

    ~ThreadPool()
    {   
        for (auto& e : _thr_pool)
        {
            e->join();
            delete(e);
        }
    }

private:
    std::vector<Thread*> _thr_pool;
    int _pnum;  // 有效线程数
};

#endif

阶段二,完善多线程安全

目标:对各线程访问任务队列进行加锁以及信号量保护。
    static void* routine(void* trda)  
    // 关于 routine函数,要设置为静态的原因:
    // 在Thread构造中,第二位为void*(*func_t)(void*)类型
    // 而类中成员函数都隐藏了一个this指针,这样会导致routine类型不匹配的问题
    {
        ThreadDate* _trda = static_cast<ThreadDate*>(trda);
        ThreadPool<T>* th = static_cast<ThreadPool<T>*>(_trda->args); 
        T task;
        while (1)  //不断获取任务
        {
            {
            LockGuard lk(th->get_mutex()); //自己封装的一个加锁类
            while (th->get_queue_empty()) 
            {
                th->wait_cond();
            }
            task = th->get_task();  //获取任务,执行...
            }
            (*task)(_trda->_name);
            sleep(1);
        }
        return nullptr;
    }

小知识积累:

1. 日志信息打印——模拟企业级日常日志记录

下面是比较标准的日志模式打印,我们可以利用条件编译的方式选择日志输出格式(终端,文件),(不过一般还好全编译为好,除非两者互斥) 

// C语言形式实现一个比较标准的日志格式
// 日志级别
#define NOWAIN 1 // 正常日志
#define DEBUG 2  // debug日志
#define WAIN 3   // 警告但能运行
#define ERROR 4  // 错误但能运行
#define FATIL 0  // 致命错误,运行停止

const char* GetlevelMap[] = {
"FATIL",
"NOWAIN",
"DEBUG",
"WAIN",
"ERROR"
};

//公司常见的日志信息:级别,时间,标准内容(文件名代码位置) + 用户自定义内容
void Logmessage(int level, const char* format , ...)
{
#ifdef REALSE  // 默认DEBUG
    // 标准日志
    char normal[1024];
    time_t tm = time(0);  // 获取时间戳
    snprintf(normal, sizeof normal, "[%s] time[%d]", GetlevelMap[level], tm);
    printf("%s\n", normal);  
#else
    // 标准日志
    char normal[1024];
    time_t tm = time(0);  // 获取时间戳
    snprintf(normal, sizeof normal, "[%s] time[%d]", GetlevelMap[level], tm);

    // 自定义日志——DEBUG阶段
    char custom_log[1024];
    va_list v_li; // 本质是 char*
    va_start(v_li, format); // 设置成format这样将打印全部自定义内容
    vsnprintf(custom_log, sizeof custom_log, format, v_li);
    printf("%s %s\n", normal, custom_log);
    va_end(v_li);
#endif
}

2. C/C++的格式化输出

 关于C语言,C++的格式化输出,在格式化输出方面,C做的相对较好,C++就只有个cout。下面是C格式输出的一些常用接口:

3. C,C++接口套用时,考虑this指针

主要是C接口调用C++类中成员函数出的问题,因为类成员函数参数里面隐藏了this指针。

阶段三,优化为单例模式——懒汉

关于 懒汉模式详细信息可看本文开头的链接
    ......
     // 通过该指令加载,使用时才进行加载——懒汉模式
    static ThreadPool<T>* Get_Instance(int num = THREADPOOL_NUM)
    {
        if (st == nullptr)
        {
            mutex_inital.lock();
            if (st == nullptr)
            {
              st =  new ThreadPool<T>(num);
            }
            mutex_inital.unlock();
        }
        return st;
    }
    ......
    ......
    int Get_queue_task_size(){return task_->size();}
    
    int Get_queue_task_reserver_size(){return task_reserver->size();}

    // 1.禁用拷贝&赋值
    ThreadPool<T>(const ThreadPool<T>& it) = delete;
    ThreadPool<T>& operator= (const ThreadPool<T>& it) = delete;

private:
    // 2.构造函数私有
    ThreadPool(int num = THREADPOOL_NUM):_pnum(num), task_(new std::queue<T>),
         task_reserver(new std::queue<T>)
    {
        for (int i = 0; i < num; i++)
        {
            _thr_pool.push_back(new Thread(i + 1, routine, this));
            // 考虑到处理方法可能还会获取进程池的信息,因此我们将进程池传入
        }

        pthread_mutex_init(&mtx_, nullptr);
        pthread_cond_init(&cond_, nullptr);
    }

    std::vector<Thread*> _thr_pool;
    int _pnum;  // 有效线程数

    std::queue<T>* task_;
    std::queue<T>* task_reserver;

    pthread_mutex_t mtx_;
    pthread_cond_t cond_; // 估计就是设置的资源量

    static ThreadPool<T>* st;
    static std::mutex mutex_inital; //这里直接用C++的锁,防止覆盖锁
};

template <class T>
ThreadPool<T>* ThreadPool<T>::st = nullptr;
template <class T>
std::mutex ThreadPool<T>::mutex_inital;

debug踩坑分享

1. 调试,不如直接打印强

2. 关于任务存放的问题,首先让我们看看上面程序的测试代码,testmain

int main()
{
    ThreadPool<Task_add*>* st = ThreadPool<Task_add*>::Get_Instance(); //加载
    st->Run();  //线程池启动
    // 主函数就负责生产任务,向任务队列输入任务
    // 未来可能是网络端获取任务
    srand(time(0) * 131 + 1);
    while (1)
    {   
        if (st->Get_queue_task_reserver_size() < QUEUE_TASK_NUM)
        {
            int x = rand() / 20 + 1;
            usleep(7777);   
            int y = rand() / 30 + 4;
            Task_add tk (x, y,  [](int a, int b){ 
            return a + b;
            });
            st->push(&tk);
        }

        // 备用队列超过5个任务再交换队列
        if (st->Get_queue_task_reserver_size() >= QUEUE_TASK_NUM / 2  &&
            st->Get_queue_task_size() == 0 &&
            st->Get_queue_task_reserver_size() <= QUEUE_TASK_NUM)
            {
                st->swap_queue();
            }
    }

不卖关子了,问题出在tk变量,我选择储存在栈上,同时循环的使用tk来创建任务,但任务队列导入的是tk地址,这就导致线程都是获取第5个任务的相同数据,解决方法:改成堆上储存,然后手动释放,这样基本上就不会出现数据覆盖的问题。

四,源码

源码: ThreadPool · 逆光/Linux - 码云 - 开源中国 (gitee.com)

结语

   本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力

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

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

相关文章

Go数据结构的底层原理(图文详解)

空结构体的底层原理 基本类型的字节数 fmt.Println(unsafe.Sizeof(0)) // 8 fmt.Println(unsafe.Sizeof(uint(0))) // 8 a : 0 b : &a fmt.Println(unsafe.Sizeof(b)) // 8int大小跟随系统字长指针的大小也是系统字长 空结构体 a : struct { }{} b : struct {…

jdk目录结构

jdk目录详解 JDK(Java Development Kit&#xff0c;Java开发包&#xff0c;Java开发工具)是一个写Java的applet和应用程序的程序开发环境。它由一个处于操作系统层之上的运行环境还有开发者 编译&#xff0c;调试和运行用Java语言写的applet和应用程序所需的工具组成。 JDK(J…

京东云轻量云主机8核16G配置租用价格1198元1年、4688元三年

京东云轻量云主机8核16G服务器租用优惠价格1198元1年、4688元三年&#xff0c;配置为8C16G-270G SSD系统盘-5M带宽-500G月流量&#xff0c;华北-北京地域。京东云8核16G服务器活动页面 yunfuwuqiba.com/go/jd 活动链接打开如下图&#xff1a; 京东云8核16G服务器优惠价格 京东云…

C语言基础语法-教案19(预处理-宏定义)

最近给大家争取到一个 深夜福利 保证你在深夜手机刷到 嘎嘎香~ 那就是 官方授权 大流量卡 缺点&#xff1a;月租太便宜 185GB~ 100分钟通话时长~ 长期套餐~ 畅想自由的气息 流量自由的同时还拥有超长通话&#xff0c;而且免费领取。 名额有限&#xff0c;咱们废话不…

流程表单平台优势明显,助力企业流程化办公!

要想提升办公效率&#xff0c;实现流程化办公&#xff0c;可以了解低代码技术平台、流程表单平台的应用价值和优势特点。在科技越来越发达和先进的今天&#xff0c;采用专业的平台和软件可以为企业带来超前的发展态势&#xff0c;创造更多市场价值。流辰信息为广大用户提供的流…

闲鱼订阅监控/上新提醒

以前闲鱼推出过一款服务&#xff0c;叫做闲鱼助手&#xff0c;帮助用户快速显示最新发布的信息。当时我也开发过一款闲鱼助手的工具。 写一个闲鱼助手的助手工具_闲鱼助手源码-CSDN博客 但是时间并不是很长&#xff0c;该功能被取消了。 最近不知道闲鱼从哪个版本开始&#x…

数字三角形(线性dp)-java

线性DP是动态规划问题中的一类问题&#xff0c;指状态之间有线性关系的动态规划问题。 文章目录 前言 一、数字三角形问题 二、算法思路 三、使用步骤 1.代码如下&#xff08;示例&#xff09;&#xff1a; 2.读入数据 3.代码运行结果 总结 前言 线性DP是动态规划问题中的一类…

工业和信息化部教育与考试中心颁发的证书有哪些?含金量如何?怎么考取?​

近期有很多网友朋友们对工业和信息化部教育与考试中心颁发的证书是否是真的证书&#xff0c;是否国家认可&#xff0c;是否全国通用&#xff0c;含金量如何&#xff1f;如何查询真假&#xff0c;以及如何报考等等相关问题有疑问&#xff0c;所以今天给大家在这里一一解答。 添加…

2.AK/SK鉴权

目录 什么是AK/SK AK/SK使用机制 时序图 什么是AK/SK 在云服务中&#xff0c;AK&#xff08;Access Key ID&#xff09;和SK&#xff08;Secret Access Key&#xff09;是访问云服务API的关键凭证对&#xff0c;主要用于身份验证和授权。AK是用户访问云服务的身份标识&…

AJ65SBTB1-32D1 三菱cc-link远程高速输入模块。

AJ65SBTB1-32D1 三菱cc-link远程高速输入模块 AJ65SBTB1-32D1用户手册, AJ65SBTB1-32D1外部连接。 AJ65SBTB1-32D1参数说明&#xff1a;DC输入32点 DC24V 5mA&#xff1b;响应时间0.2ms&#xff1b;32点1公共端&#xff1b;1线式&#xff1b;正/负公共端共用&#xff1b;端子排…

[StartingPoint][Tier1]Responder

Important 由于靶机IP是动态的,所以这里需要手动解析 # echo "<靶机IP> unika.htb">>/etc/hosts //10.10.16.59/testshare到底是什么? SMB&#xff08;Server Message Block&#xff09;是一种用于在计算机之间共享文件、打印机和其他资源的网络协议&…

1995-2021年各省分品种能源产量和消费量数据

1995-2021年各省分品种能源产量和消费量数据 1、时间&#xff1a;1995-2021年 2、来源&#xff1a;能源统计年鉴、各省年鉴 3、指标&#xff1a;能源消费总量、煤炭消费量、焦炭消费量、原油消费量、汽油消费量、煤油消费量、柴油消费量、燃料油消费量、天然气消费量、电力消…

Java入门基础知识第六课(超基础,超详细)——循环结构

前面二白讲了选择结构相关知识&#xff0c;主要是if选择结构和swich选择结构&#xff0c;这次咱们讲一下循环结构&#xff0c;主要是while、do-while、for这三种循环结构 一、while循环结构 语法&#xff1a; 初始值代码; while(循环条件){ 循环操作代码块; 迭代代码; } 执行…

产品经理考个PMP有用吗?

产品经理考PMP认证考试是否有用&#xff0c;这个问题答案是肯定的。项目管理作为一项通用管理技能&#xff0c;如果产品经理能够掌握&#xff0c;对产品设计和管理工作是十分有益的。 产品经理是企业中专门负责产品管理的职位&#xff0c;其负责明确产品需求和产品设计&#x…

关于goto的一点说明

1、goto的label是会被顺序执行的 如下例所示&#xff0c;error也会被执行。 #include <iostream>void test(bool flag) {if (flag){printf("--------------- yes.\n");}else {goto error;}error:printf("error.\n"); }int main() {std::cout <&l…

pytest教程-23-指定用例执行顺序插件-pytest-ordering

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go 上一小节我们学习了pytest用例依赖插件-pytest-dependency,本小节我们讲解一下pytest指定用例执行顺序插件-pytest-ordering。 pytest在执行用例的时候&#xff0c;默认是按照文件中用例的先后顺序执行&#xff…

达梦数据库记录

1.计算日期差 SELECT DATEDIFF(day,sysdate(), 2024-06-01) 2.出现HJ_BUF_GLOBAL_SIZE设置不当造成应用报错的问题&#xff0c;详细信息如下&#xff1a; dm.jdbc.driver.DMException: 超出全局hash join空间,适当增加HJ_BUF_GLOBAL_SIZEat dm.jdbc.driver.DBError.throwExce…

opencv图像处理技术(阈值处理与图像平滑)

进行图像处理时&#xff0c;常常需要对图像进行预处理以提取所需的信息或改善图像质量。阈值处理和图像平滑是两种常见的预处理技术。 阈值处理 阈值处理是一种图像分割技术&#xff0c;其基本思想是将图像中的像素值与一个或多个预先设定的阈值进行比较&#xff0c;根据比较…

VIM支持C/C++/Verilog/SystemVerilog配置并支持Win/Linux环境的配置

作为一个芯片公司打杂人口&#xff0c;同时兼数字IC和软件&#xff0c;往往需要一个皮实耐打上天入地的编辑器… 一、先附上github路径&#xff0c;方便取走 git clone gitgithub.com:qqqw4549/vim_config_c_verilog.git 二、效果展示 支持ctrl]函数/模块跳转&#xff0c;支持…

LeetCode-46. 全排列【数组 回溯】

LeetCode-46. 全排列【数组 回溯】 题目描述&#xff1a;解题思路一&#xff1a;回溯。回溯三部曲解题思路二&#xff1a;0解题思路三&#xff1a;0 题目描述&#xff1a; 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案…