Linux 线程池

2023-05-16

文章目录

    • 线程池的定义
    • 使用线程池的原因
    • 基于POSIX实现的线程池
      • 基于block队列的线程池实现
      • 基于ring队列的线程池实现
    • 设计单例模式线程池

线程池的定义

线程池就一堆已经创建好的任务线程,初始它们都处于空闲等待状态,当有新的任务需要处理的时候,就从这线程池里取一个空闲的线程来处理任务,当任务处理完成后再次把线程返回线程池里(把线程置于空闲等待状态),以供后面线程继续使用。当线程池里所有的线程都处于忙碌状态时,可以根据情况进行等待或创建一个新的线程放入线程池里。

使用线程池的原因

线程的创建和销毁相对于进程的创建和销毁来说是轻量级的(即开销小),但是当我们的任务需要进行大量线程的创建和销毁时,这些开销合在一起就比较大了。比如,当设计一个压力性能测试框架时,需要连续产生大量的并发操作。线程池在这种场合是非常适用的。线程池的好处就在于线程复用,某个线程在处理完一个任务后,可以继续处理下一个任务,不用重新创建和销毁,避免了无谓的开销,因此线程池适用于连续产生大量并发任务的场合。

基于POSIX实现的线程池

在了解线程池的基本原理,下面我们用c++传统的方式也就POSIX来实现一个基本的线程池,该线程池虽然简单,但能体现线程池的基本工作原理。线程池的实现千变万化,有时候要根据实际的场合来定制,当原理都是一样的,

基于block队列的线程池实现

  • 实现block队列
  • 创建一堆线程,线程从队列拿取任务
#pragma once

#include <pthread.h>
#include <iostream>
#include <queue>
#include <semaphore.h>
#include<unistd.h>
// 总结:我们应该清楚生产者消费者模型的目的是为了让生产和消费解耦,解耦的好处在于,生产和消费能并发执行,
// 在多生产者多消费者模型中,目的就为了能合理控制生产消费线程的个数达到合理利用资源。
namespace ns_block_queue_pthread_pool
{
    template <class T>
    class pthread_pool
    {
    private:
        int num_;                        // 线程数量
        std::queue<T> task_queue_;       // 任务队列,供给线程池使用
        pthread_cond_t task_queue_cond_; // 任务队列的条件变量

        // 拿取数据的过程要消费者间互斥,消费者与生产者互斥同步,所以我们需要添加锁来进行保护,并且加入条件变同步
        pthread_mutex_t mtx_; // 多生产者多消费者维护关系的共有锁资源,

    private:
        // 对成员变量的访问目的为了让静态函数通过接收this参数访问成员函数并且访问成员变量。
        void Lock()
        {
            pthread_mutex_lock(&mtx_);
        }
        void UnLock()
        {
            pthread_mutex_unlock(&mtx_);
        }
        bool IsEmpty()
        {
            return task_queue_.empty();
        }
        void WakeUpThread()
        {
            pthread_cond_signal(&task_queue_cond_);
        }

    public:
        // 构造函数
        pthread_pool(int num /*线程数量*/)
            : num_(num)
        {
            pthread_cond_init(&task_queue_cond_, nullptr);
            pthread_mutex_init(&mtx_, nullptr);
        }
        // 析构函数
        ~pthread_pool()
        {
            pthread_cond_destroy(&task_queue_cond_);
            pthread_mutex_destroy(&mtx_);
        }
        void TaskPop(T &out)
        {
            // 拿取数据的过程要消费者间互斥,消费者与生产者互斥同步,所以我们需要添加锁来进行保护,并且加入条件变同步
            Lock();
            // 线程在满拿取条件时拿取数据处理,否则等待条件变量
            while (IsEmpty())
            {
                // 线程池的线程充当消费者
                pthread_cond_wait(&task_queue_cond_, &mtx_);
            }
            out = task_queue_.front();
            task_queue_.pop();
            UnLock();
        }
        void TaskPush(T &in)
        {
            Lock();
            task_queue_.push(in);
            UnLock();
            WakeUpThread();
        }
        // 线程池线程
        // 我们必须设置成静态的函数,原因是成员函数有this参数
        static void *routine(void *agrs)
        {
            //
            pthread_pool<T> *pp = (pthread_pool<T> *)agrs;
            while (true)
            {
                T task;
                pp->TaskPop(task);
                // 执行任务;
                std::cout << "线程:" << pthread_self() << "执行数据:" << task << "执行任务完成" << std::endl;
            }
        }
        // 初始化线程池
        void PthreadPoolInit()
        {
            pthread_t id;
            for (int i = 0; i < num_; i++)
            {
                pthread_create(&id, nullptr, routine, this /*线程池对象this指针*/);
            }
        }
    };
}

基于ring队列的线程池实现

  • 实现ring队列
  • 创建一堆线程
namespace ns_ring_queue_pthread_pool
{
    template <class T>
    class pthread_pool
    {
        const int queue_cap = 5;

    private:
        int num_;                  // 线程数量
        std::vector<T> task_queue; // 任务队列,供给线程池使用
        size_t p_pos;
        size_t c_pos;
        sem_t q_blank;
        sem_t q_data;1
        pthread_mutex_t mtx_; // 多生产者多消费者维护关系的共有锁资源,
        pthread_mutex_t c_mutex;
        pthread_mutex_t p_mutex;

    private:
        // 对成员变量的访问目的为了让静态函数通过接收this参数访问成员函数并且访问成员变量。
        void Lock(pthread_mutex_t &mutex)
        {
            pthread_mutex_lock(&mutex);
        }
        void UnLock(pthread_mutex_t &mutex)
        {
            pthread_mutex_unlock(&mutex);
        }

    public:
        // 构造函数
        pthread_pool(int num /*线程数量*/)
            : num_(num), c_pos(0), p_pos(0)
        {
            task_queue.resize(queue_cap);
            pthread_mutex_init(&mtx_, nullptr);
            pthread_mutex_init(&p_mutex,nullptr);
            pthread_mutex_init(&c_mutex,nullptr);
            sem_init(&q_blank, NULL, queue_cap);
            sem_init(&q_data, NULL, 0);
            std::cout<<"pthread_pool 初始化成功"<<std::endl;
        }
        // 析构函数
        ~pthread_pool()
        {
            sem_destroy(&q_blank);
            sem_destroy(&q_data);
            pthread_mutex_destroy(&mtx_);
            pthread_mutex_destroy(&c_mutex);
            pthread_mutex_destroy(&p_mutex);
        }
        void TaskPop(T &out)
        {
            sem_wait(&q_data);
            Lock(c_mutex);
            out = task_queue[c_pos];
            sem_post(&q_blank);
            c_pos++;
            c_pos %= queue_cap;
            UnLock(c_mutex);
        }
        void TaskPush(T &in)
        {
            sem_wait(&q_blank);
            
            Lock(p_mutex);
            task_queue[p_pos] = in;
            sem_post(&q_data);
            p_pos++;
            p_pos %= queue_cap;

            UnLock(p_mutex);
        }
        // 线程池线程
        // 我们必须设置成静态的函数,原因是成员函数有this参数
        static void *routine(void *agrs)
        {
            //
            pthread_pool<T> *pp = (pthread_pool<T> *)agrs;
            while (true)
            {
                T task;
                pp->TaskPop(task);
                // 执行任务;
                std::cout << "线程:" << pthread_self() << "执行数据:" << task << "执行任务完成" << std::endl;
                sleep(4);
            }
        }
        // 初始化线程池
        void PthreadPoolInit()
        {
            pthread_t id;
            for (int i = 0; i < num_; i++)
            {
                pthread_create(&id, nullptr, routine, this /*线程池对象this指针*/);
            }
        }
    };
}

注意:
这里没有对线程池销毁线程操作,声明周期随进程。

设计单例模式线程池

线程池本身会在任何场景,任何环境下被调用,因此我们可以设计成单例模式,由一个单例对线程池进行管理。

#pragma once

#include <pthread.h>
#include <iostream>
#include <queue>
// 总结:我们应该清楚生产者消费者模型的目的是为了让生产和消费解耦,解耦的好处在于,生产和消费能并发执行,
// 在多生产者多消费者模型中,目的就为了能合理控制生产消费线程的个数达到合理利用资源。
namespace ns_pthread_pool
{
    const int g_default_val = 3;
    template <class T>
    class pthread_pool
    {
    private:
        int num_;                        // 线程数量
        std::queue<T> task_queue_;       // 任务队列,供给线程池使用
        pthread_cond_t task_queue_cond_; // 任务队列的条件变量

        // 拿取数据的过程要消费者间互斥,消费者与生产者互斥同步,所以我们需要添加锁来进行保护,并且加入条件变同步
        pthread_mutex_t mtx_; // 多生产者多消费者维护关系的共有锁资源,

        static pthread_pool<T> *ins_;

        // 私有构造函数
        pthread_pool(int num = g_default_val /*线程数量*/)
            : num_(num)
        {
            pthread_cond_init(&task_queue_cond_, nullptr);
            pthread_mutex_init(&mtx_, nullptr);
        }

    private:
        // 对成员变量的访问目的为了让静态函数通过接收this参数访问成员函数并且访问成员变量。
        void Lock()
        {
            pthread_mutex_lock(&mtx_);
        }
        void UnLock()
        {
            pthread_mutex_unlock(&mtx_);
        }
        bool IsEmpty()
        {
            return task_queue_.empty();
        }
        void WakeUpThread()
        {
            pthread_cond_signal(&task_queue_cond_);
        }

    public:
        static pthread_pool<T> *GetInstance()
        {
            static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
            // 当前单例对象还没有被创建
            if (ins_ == nullptr) // 双判定,减少锁的争用,提高获取单例的效率!
            {
                pthread_mutex_lock(&lock);

                // ins为空说明该类没有创建过对象
                if (ins_ == nullptr)
                {
                    ins_ = new pthread_pool<T>();
                    ins_->PthreadPoolInit();
                    std::cout << "该类第一次创建对象" << std::endl;
                }
            }
            return ins_;
        }
        pthread_pool(const pthread_pool<T> &pool) = delete;
        pthread_pool<T> &operator=(const pthread_pool<T> &pool) = delete;

        // 析构函数
        ~pthread_pool()
        {
            pthread_cond_destroy(&task_queue_cond_);
            pthread_mutex_destroy(&mtx_);
        }
        void TaskPop(T &out)
        {
            // 拿取数据的过程要消费者间互斥,消费者与生产者互斥同步,所以我们需要添加锁来进行保护,并且加入条件变同步
            Lock();
            // 线程在满拿取条件时拿取数据处理,否则等待条件变量
            while (IsEmpty())
            {
                // 线程池的线程充当消费者
                pthread_cond_wait(&task_queue_cond_, &mtx_);
            }
            out = task_queue_.front();
            task_queue_.pop();
            UnLock();
        }
        void TaskPush(T &in)
        {
            Lock();
            task_queue_.push(in);
            UnLock();
            WakeUpThread();
        }
        // 线程池线程
        // 我们必须设置成静态的函数,原因是成员函数有this参数
        static void *routine(void *agrs)
        {
            //
            pthread_pool<T> *pp = (pthread_pool<T> *)agrs;
            while (true)
            {
                T task;
                pp->TaskPop(task);
                // 执行任务;
                std::cout << "线程:" << pthread_self() << "执行数据:" << task() << "执行任务完成" << std::endl;
            }
        }
        // 初始化线程池
        void PthreadPoolInit()
        {
            pthread_t id;
            for (int i = 0; i < num_; i++)
            {
                pthread_create(&id, nullptr, routine, this /*线程池对象this指针*/);
            }
        }
    };
    template <class T>
    pthread_pool<T> *pthread_pool<T>::ins_ = nullptr;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux 线程池 的相关文章

  • GFF/GTF简介及格式转换

    最近做转录组的比对时 xff0c 在建立索引过程中 xff0c 遇见一个问题 xff0c 就是我从ncbi下载的序列文件和gtf文件中 xff0c 染色体命名规则竟然不一样 xff0c 但序列文件和gff文件染色体命名规则是一样的 xff0
  • Linux 系统上安装R及加载R包

    因为安装Hic Pro xff0c 需要依赖几个R包 xff0c 比如ggplot2 又依赖 gt 4 0的R xff0c 之前安的3 6 xff0c 再重新安装一遍最新的吧 xff0c 记录一下 xff0c 省去了以后再重复查资料的过程
  • BWA比对及Samtools提取目标序列

    今天想看一下自己的序列里面会不会有某细菌基因组存在 xff0c 主要使用BWA和Samtools xff1a bwa主要用于将低差异度的短序列与参考基因组进行比对 主要包含三种比对算法 xff1a backtrack SW和MEM xff0
  • 核糖体rRNA分类-功能应用-数据库-Silva

    一 xff0e 分类 xff1a 原核生物的rRNA分三类 xff1a 5SrRNA 16SrRNA和23SrRNA 真核生物的rRNA分四类 xff1a 5SrRNA 5 8SrRNA 18SrRNA和28SrRNA S为大分子物质在超速
  • RepeatMasker基因组重复序列检测工具安装及使用

    一 RepeatMasker简介 基因组组装完成后 xff0c 进行基因预测和注释 由于基因组中存在重复序列结构区 xff0c 特别是高等真核生物 xff0c 重复序列占了相当大的比例 xff0c 会影响基因预测的质量 xff0c 也会带来
  • Seqkit:强大的序列处理工具

    Seqkit是一款专门处理fsata q序列文件的软件 github地址 https github com shenwei356 seqkit 下载地址 xff1a https bioinf shenwei me seqkit downlo
  • Minimap2:三代比对工具

    在使用Purge dups去冗余时 xff0c 用到了Minimaps2 xff0c 把学习的东西整理一下 先声明本文软件介绍的很多内容来自 生信算法 公众号文章 xff0c 用来作为自己的学习记录 xff0c 原文作者若不同意的话就删稿
  • 白盒测试:语句覆盖、条件覆盖、判定覆盖、条件-判定覆盖、组合覆盖、路径覆盖

    语句覆盖 xff1a 所有的 语句 都要覆盖一遍 判定覆盖 xff1a 包含语句覆盖 xff0c 每个判断T F各一次 条件覆盖 xff1a 包含语句覆盖 xff0c 每个条件T F各一次 判定条件覆盖 xff1a 包含判定覆盖 条件覆盖
  • GMAP一款比对工具用于ALLHiC构建等位基因表

    在ALLHiC使用过程中需要构建Allele ctg table xff0c 用于过滤多倍体基因组中因等位序列相似引起的HiC噪音的必要输入 官网提供了两种办法 xff0c 一种是blastn xff0c 需要对草图基因组进行注释 xff0
  • 数据结构——平衡二叉树(AVL树)之插入

    文章目录 前言一 定义二 基本操作1 查找 xff0c 2 插入 如何调整 如何调整代码实现插入 前言 首先我们来思考一下一个普通二叉树保存数据 xff0c 如果想查找一个数据 xff0c 由于普通二叉树保存数据是随机的 xff0c 要找到
  • C++之引用怎么用

    1 引用的概念 引用并不是新定义一个变量 xff0c 而是给一个已存在的变量取一个别名 编译器并不会为引用变量开辟空间 xff0c 它和它应用的变量共用一块空间 也就是说引用是同一块变量空间的不同名字 格式 xff1a 类型 amp 引用变
  • 完全背包问题

    目录 一 什么是完全背包 二 完全背包问题的里外层循环可以交换吗 三 题 3 1 求组合数 3 2 求排列和 3 3 求最小值 一 什么是完全背包 完全背包问题一般是指 xff1a 有N件物品和一个能背重量为W的背包 xff0c 第i件物品
  • 如何从GitHub上下载开源项目

    作为开源代码库以及版本控制系统 xff0c Github拥有超过900万开发者用户 随着越来越多的应用程序转移到了云上 xff0c Github已经成为了管理软件开发以及发现已有代码的首选方法 GitHub上有无数优秀开发者正在开发和维护的
  • 进程间通信之共享内存

    目录 一 共享内存实现进程间通信的原理 二 管理共享内存的数据结构 三 共享内存函数 四 实现进程间通信 接博客 xff1a 进程间通信之管道 一 共享内存实现进程间通信的原理 共享内存实际是操作系统在实际物理内存中开辟的一段内存 共享内存
  • ICMP协议详解

    ICMP协议 一 概念 ICMP协议是一个网络层协议 和IP协议处于同一层 xff0c 但是ICMP协议底层用的是IP协议 一个搭建好的网络 xff0c 往往需要先进行简单的测试 xff0c 来验证网络是否通畅 单单使用IP协议并不提供可靠
  • C++11——右值引用

    目录 前言 一 右值引用的概念 1 1 左值和右值的概念 1 2 引用和右值引用比较 二 右值引用的作用 2 1引用的缺陷 2 1 移动语义 2 2 右值引用的具体应用 2 3 对比引用总结 三 右值引用引用左值 move 四 完美转化 前
  • C++11——lambda表达式

    目录 前言 一 lambda表达式用法 二 lambda表达式语法 三 lambda表达式的原理 前言 在显示生活中 xff0c 我们在用手机购物时 总是可以在页面上看到下面这样的选项 我们知道底层这是通过排序来完成的 xff0c 但是当我
  • MySQL索引

    目录 前言 一 认识磁盘 二 MySQL与磁盘的交互基本单位 三 索引的理解 3 1 引出索引 3 2 MySQL管理Page 3 2 1 单个Page的情况 3 2 2 多page的情况 3 3 什么是索引 四 聚簇索引和非聚簇索引 4
  • 100道测试工程师笔试的Linux笔试题及答案

    单选题 xff1a 1 cron 后台常驻程序 daemon 用于 xff1a A 负责文件在网络中的共享 B 管理打印子系统 C 跟踪管理系统信息和错误 D 管理系统日常任务的调度 2 在大多数Linux发行版本中 xff0c 以下哪个属
  • Redis应用问题及解决

    目录 一 缓存穿透 1 1 问题描述 1 2 解决方案 二 缓存击穿 2 1 问题描述 2 2 解决方案 三 缓存雪崩 3 1 问题描述 3 2 解决方案 当数据库压力变大 xff0c 导致服务访问数据库响应变慢 xff0c 导致服务的压力

随机推荐

  • Shell脚本练习

    求100以内正奇数和 注意点 xff1a 和 xff1a 是进行数学运算的 支持 43 xff1a 分别为 加 减 乘 除 取模 但是注意 xff0c bash只能作整数运算 xff0c 对于浮点数是当作字符串处理的 a b xff1a 表
  • sed命令_Linux sed命令:替换、删除、更新文件中的内容

    sed 是 stream editor 的缩写 xff0c 中文称之为 流编辑器 sed 命令是一个面向行处理的工具 xff0c 它以 行 为处理单位 xff0c 针对每一行进行处理 xff0c 处理后的结果会输出到标准输出 xff08 S
  • 《国产操作系统之银河麒麟》银河麒麟服务器操作系统引导过程

    目录 系统引导过程 01 系统启动流程概述 系统启动总流程 第一阶段 xff1a BIOS初始化 编辑 第二阶段 GRUB2启动引导 编辑 第三阶段 内核引导 编辑 第四阶段 systemd进程 02 固件与BIOS BIOS启动流程 BI
  • Spring MVC基础配置

    Spring MVC 使用步骤 xff1a 1 在web xml中的配置DispatcherServlet span class token tag span class token tag span class token punctua
  • 对Redis布隆过滤器的实现

    目录 实现思路 首先最重要的自定义hash 然后就是将key放入bitSet 然后就是判断布隆过滤器bitSet数组中是否含有对应的key 代码 实现思路 39条消息 Redis布隆过滤器 Fairy要carry的博客 CSDN博客 首先最
  • Keil uVision5修改工程名字

    目录 1 打开文件中mdk的文件夹 xff1a 2 把Listings和Objects里边东西全部删除 xff1a 3 删除文件夹之外的东西 xff0c 并且绿色这个文件改名 xff1a 4 打开Keil uVision5 xff0c 点开
  • Redis的Java客户端

    1 快速入门 Jedis使用的基本步骤 xff1a 1 引入依赖 2 创建Jedis对象 xff0c 建立连接 3 使用Jedis xff0c 方法名与Redis命令一致 4 释放资源 lt jedis依赖 gt lt dependency
  • linux下使用rpm安装mysql

    1 get mysql rpm package mysql rpm install https www aliyundrive com s 6xUyXcdqYJF 点击链接保存 xff0c 或者复制本段内容 xff0c 打开 阿里云盘 AP
  • python爬取豆瓣T250电影及保存excel(易上手)

    网址 xff1a 豆瓣电影 Top 250 目录 一 bs4和re正则爬取 二 xpath爬取 一 bs4和re正则爬取 源代码 xff1a import urllib request urllib error import re from
  • qt发布的程序时如何将依赖的dll分开放在不同目录

    SetDllDirectory设定DLL加载路径 include 34 Windows h 34 切换工作目录 xff0c 到指定目录查找依赖的dll文件 QString s 61 34 debug 34 LPCWSTR path 61 s
  • Android项目目录结构和资源管理

    项目目录结构和资源管理 项目目录结构默认结构形式真正的结构形式app包里结构src包里的目录 资源的管理和使用图片资源布局资源字符串资源样式颜色资源 项目目录结构 默认结构形式 新建的项目会默认使用Android模式的项目结构 xff0c
  • jupyter notebook无法打开(或无法用终端打开)

    报错如下 xff1a 解决方法 xff1a 添加这三个环境变量 注 xff1a 这三个路径虽然短 xff0c 但是一定要复制粘贴进去 xff0c 手写很容易报错 xff0c 即使你路径手写是对的 其他问题解决方法 xff1a xff08 1
  • Spring Aop通知注解的执行顺序

    spring4和spring5有所不同 spring4没异常有异常执行顺序从上往下 64 Around通知前 64 Aroud通知前 64 Before通知 64 Before通知业务代码 64 After通知 64 Around通知后 6
  • vcruntime140_1.dll无法继续执行代码如何修复?

    vcruntime140 1 dll是电脑系统动态链接中非常重要的文件 xff0c 主要用于处理各种程序 每台计算机上都有相当多的DLL文件 xff0c 不同的程序会使用不同的DLL文件 电脑系统如果丢失dll文件 xff0c 会导致很多软
  • Linux基础指令的基本操作(一)

    文章目录 Linux用户管理 xff1a 1 adduser添加用户2 passwd修改用户密码3 userdel删除用户 其他指令alias指令 取别名 whoami指令man指令 重要 bc指令unamefreedf h Linux 访
  • Linux 权限(二)权限掩码 粘滞位 详细

    文章目录 Linux权限的概念Linux权限管理01 文件访问者的分类 xff08 人 xff09 02 文件类型和访问权限 xff08 事物属性 xff09 拥有者 xff0c 所属组 xff0c other vs root 和普通用户a
  • Linux——基础IO

    文章目录 先来段代码回顾C文件接口写文件读文件输出信息到显示器 xff0c 你有哪些方法 默认打开的三个流 stdin amp stdout amp stderr系统接口openclosewriteread文件描述符fd文件描述符的分配规则
  • boost字符串库简单使用

    boost字符串库简单使用 说明用法大小写转换字符串分割去掉字符串两边空格替换字符串 replace first replace first copy 说明 写c 43 43 程序的时候 xff0c 虽然std string有数百余函数 x
  • 线程安全下单例模式

    文章目录 什么是单例模式单例模式的特点定义对象的本质什么时候创建对象饿汉实现方式和懒汉实现方式饿汉方式实现单例模式懒汉方式实现单例模式懒汉方式实现单例模式 线程安全版本 什么是单例模式 单例模式是一种 经典的 常用的 常考的 设计模式 单例
  • Linux 线程池

    文章目录 线程池的定义使用线程池的原因基于POSIX实现的线程池基于block队列的线程池实现基于ring队列的线程池实现 设计单例模式线程池 线程池的定义 线程池就一堆已经创建好的任务线程 xff0c 初始它们都处于空闲等待状态 xff0