【Linux】线程实例 | 简单线程池

2023-05-16

今天来写一个简单版本的线程池

1.啥是线程池

池塘,顾名思义,线程池就是一个有很多线程的容器。

我们只需要把任务交到这个线程的池子里面,其就能帮我们多线程执行任务,计算出结果。

与阻塞队列不同的是,线程池中内有一个队列用于任务管理,并帮我们封装了线程创建的工作。我们不再需要在主执行流里面创建线程(创建线程也是有时间消耗的),而是只关注于任务的创建,交给线程池来运行并产生结果就OK了

前面已经学习过阻塞队列了,此时再来写线程池,就没有那么困难了!

本次线程池的设计还会采用单例模式,同一个模板类型的任务,只需要一个线程池即可

1.1 简单复习单例模式

单例模式分为两种设计方式,一个是懒汉,一个是饿汉

  • 懒汉:刚开始先不创建单例,等第一次使用的时候在创建;缺点是第一次获取单例需要等待,优点是程序启动快
  • 饿汉:main函数执行前,就将单例创建起来;缺点是程序启动会比较慢,优点是启动之后获取单例会快

2.代码示例-处理task

2.1 成员变量

因为是线程池,需要在内部创建出线程来运行,所以我们需要一个num来标识需要创建的线程的数量

template <class T>
class ThreadPool{
private:
	bool _isStart;  // 线程池子是否启动
    int _threadNum; // 线程数量
    queue<T> _tq;   // 任务队列
    pthread_mutex_t _mutex;// 锁
    pthread_cond_t _cond;  // 条件变量
    static ThreadPool<T> *instance; // 单例模式需要用到的指针
}

这里我们并不需要弄一个数组来存放已经创建的线程,因为我们并不关心线程的退出信息,也不需要对线程进行管理。在创建好线程之后,直接detach即可

static变量我们需要在类外初始化,因为是模板类型,所以还需要带上template关键字

// 初始化static变量
template <class T>
ThreadPool<T> *ThreadPool<T>::instance = nullptr;

2.2 构造/析构

本次使用的是懒汉模式的单例,提供一个指针作为单例,不开放构造函数

private:
    ThreadPool(int num = DEFALUT_NUM)
        : _threadNum(num),
          _isStart(false)
    {
        assert(num > 0);
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }
    ThreadPool(const ThreadPool<T> &) = delete;//取消拷贝
    void operator=(const ThreadPool<T> &) = delete;//取消赋值

同时,利用delete关键字,禁止拷贝构造和赋值重载;析构依旧保持公有

    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }

这种情况下,我们还需要有一个static成员函数来获取单例;在之前的单例模式博客中,提到当初实现的懒汉模式是线程不安全的,因为没有对线程进行加锁,避免多个执行流同时获取单例,导致单例对象冲突的问题。

现在学习了linux的加锁操作,就可以避免掉这个bug了

两次nullptr判断

其中关于两次nullptr判断的原因,详见注释

  • 第一个判断是为了保证单例,只要单例存在了,就不再创建单例
  • 第二个判断是保证线程安全,可能会出现线程a在创建单例,线程b在锁中等待的情况;此时如果不进行第二次nullptr判断,线程b从锁中被唤醒后,又会继续执行,多创建了一个单例!
public:
    static ThreadPool<T> *getInstance()
    {
        static pthread_mutex_t mt;//使用static,只会创建一次;避免多次实例化,一个执行流一个锁,失去效果
        pthread_mutex_init(&mt,nullptr);
        if (instance == nullptr) // 第一次判断
        {
            pthread_mutex_lock(&mt);// 加锁,保证只有一个执行流走到这里
            if (instance == nullptr)// 第二次判断是来确认的,避免出现在加锁前,被其他执行流获取过实例了
            {
                instance = new ThreadPool<T>();// 确认是null,创建单例
            }
        }

        pthread_mutex_unlock(&mt);
        pthread_mutex_destroy(&mt);
        return instance;
    }

2.3 启动线程池

有了线程池,接下来要做的就是启动它😁

启动之前,我们需要assert判断一下该线程池是否已经启动了,避免多次启动线程池出现问题。启动完成之后,更新isStart的状态值

    void start()
    {
        assert(!_isStart);//如果开启了,那么就不能执行该函数
        for (int i = 0; i < _threadNum; i++)
        {
            pthread_t temp;
            pthread_create(&temp, nullptr, threadRoutine, this);//把this当参数传入
            usleep(100);
            pthread_detach(temp);//分离线程
        }
        _isStart = true;//标识状态,代表线程池已经启动了
    }

这里还有另外一个函数threadRoutine,这是每一个线程需要执行的函数,其为static函数。这里我们获取到的都是单例的this指针,访问成员都需要通过this指针来访问

static void *threadRoutine(void *args)
{
    ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);//c++强转
    while (1)
    {
        tp->lockQueue();
        while (!tp->haveTask())
        {
            tp->waitForTask();
        }
        // 任务被拿到了线程的上下文中
        T t = tp->pop();
        tp->unlockQueue();

        // 规定每一个封装的task对象都需要有一个run函数
        t.resultPrint(t.run());//运行并打印结果
    }
}

2.4 封装的加锁/解锁/通知操作

这部分操作比较简单,就不多提了。其实就是把已有的函数改个名字,变成无参可直接调用的函数罢了。

private:
    void lockQueue() { pthread_mutex_lock(&_mutex); }
    void unlockQueue() { pthread_mutex_unlock(&_mutex); }
    bool haveTask() { return !_tq.empty(); }
    void waitForTask() { pthread_cond_wait(&_cond, &_mutex); }
    void singalThread() { pthread_cond_signal(&_cond); }
    T pop()
    {
        T temp = _tq.front();
        _tq.pop();
        return temp;
    }

其中pop()函数设置为了私有,因为线程池会自己开始处理任务,所以不需要外部pop


2.5 插入任务

最后就只剩下任务的插入了,插入一个任务后,使用条件变量,唤醒线程池中的一个线程来执行这个任务!

    //往线程池中给任务
    void push(const T &in)
    {
        lockQueue();
        _tq.push(in);//插入任务
        singalThread();//任务插入后,唤醒一个线程来执行
        unlockQueue();
    }

到这里,线程池就大功告成了!

3.测试

本次测试依旧使用了在线程博客中提到过的task.hpp,完整代码详见我的gitee仓库

因为使用了线程池,主执行流只需要来派发任务即可;

#include "threadpool.hpp"
#include "task.hpp"
#include <string>
#include <time.h>

int main()
{
    const string operators = "+/*/%";
    ThreadPool<Task>*tp = ThreadPool<Task>::getInstance();
    tp->start();

    srand((unsigned long)time(nullptr) ^ getpid() ^ pthread_self());
    // 派发任务的线程
    while(1)
    {
        int one = rand()%50;
        int two = rand()%10;
        char oper = operators[rand()%operators.size()];
        cout << "[" << pthread_self() << "] 主线程派发计算任务: " << one << oper << two << "=?" << "\n";
        Task t(one, two, oper);
        tp->push(t);
        sleep(1);
    }
    
}

此时线程池就会帮我们运行,并将结果输出!

[muxue@bt-7274:~/git/linux/code/23-01-18 threadpool]$ ./test
[140202992179008] 主线程派发计算任务: 14/8=?
[140202973767424] 新线程完成计算任务: 14/8=1
[140202992179008] 主线程派发计算任务: 43*2=?
[140202965374720] 新线程完成计算任务: 43*2=86
[140202992179008] 主线程派发计算任务: 10/9=?
[140202956982016] 新线程完成计算任务: 10/9=1
[140202992179008] 主线程派发计算任务: 25*9=?
[140202948589312] 新线程完成计算任务: 25*9=225
[140202992179008] 主线程派发计算任务: 8/0=?
div zero, abort
[140202940196608] 新线程完成计算任务: 8/0=-1
[140202992179008] 主线程派发计算任务: 38%1=?
[140202973767424] 新线程完成计算任务: 38%1=0
[140202992179008] 主线程派发计算任务: 23/7=?
[140202965374720] 新线程完成计算任务: 23/7=3
[140202992179008] 主线程派发计算任务: 4%4=?
[140202956982016] 新线程完成计算任务: 4%4=0
[140202992179008] 主线程派发计算任务: 44*8=?
[140202948589312] 新线程完成计算任务: 44*8=352
[140202992179008] 主线程派发计算任务: 4/2=?

3.1 修改轻量级进程的名字

Linux提供了一个有趣的接口,可以允许我们修改轻量级进程的名字;

没有修改的时候,默认的名字都是该进程的可执行程序的名字

[muxue@bt-7274:~/git/linux/code/23-01-18 threadpool]$ ps -aL
  PID   LWP TTY          TIME CMD
 6592  6592 pts/7    00:00:00 test
 6592  6593 pts/7    00:00:00 test
 6592  6594 pts/7    00:00:00 test
 6592  6595 pts/7    00:00:00 test
 6592  6596 pts/7    00:00:00 test
 6592  6597 pts/7    00:00:00 test
 6730  6730 pts/8    00:00:00 ps

我们使用prctl接口,修改名字;这个接口的作用是对一个进程进行操作。

#include <sys/prctl.h>
int prctl(int option, unsigned long arg2, unsigned long arg3,
		unsigned long arg4, unsigned long arg5);

其中修改线程名字的操作如下

prctl(PR_SET_NAME, "handler");//修改线程名字为handler

分别修改主执行流和线程池中线程的名字,即可获得不一样的结果

[muxue@bt-7274:~/git/linux/code/23-01-18 threadpool]$ ps -aL
  PID   LWP TTY          TIME CMD
 7793  7793 pts/7    00:00:00 master
 7793  7794 pts/7    00:00:00 handler
 7793  7795 pts/7    00:00:00 handler
 7793  7796 pts/7    00:00:00 handler
 7793  7797 pts/7    00:00:00 handler
 7793  7798 pts/7    00:00:00 handler
 7828  7828 pts/8    00:00:00 ps

这样可以用于标识线程的属性,还是有些用的!

The end

本篇博客到这里就over啦,有啥问题欢迎评论区提出哦!

QQ图片20220413084241

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Linux】线程实例 | 简单线程池 的相关文章

  • 38、带参数的装饰器

    一 带参数的装饰器 我们看 xff0c 装饰器其实就是一个闭包函数 xff0c 再说简单点就是两层的函数 那么是函数 xff0c 就应该具有函数传参功能 login status 61 39 username 39 None 39 stat
  • Git系列:windows10安装Git版本控制工具!

    Git系列 xff1a windows10安装Git版本控制工具 xff01 前言 今天博主将为大家分享Git系列 xff1a windows10安装Git版本控制工具 xff01 不喜勿喷 xff0c 如有异议欢迎讨论 xff01 开始安
  • VNC 连接腾讯云服务器(解决黑屏、灰屏问题)

    整了一天 xff0c 要么黑屏 xff0c 要么灰屏 xff0c 巴拉巴拉 xff0c 最后终于好了 vnc xstartup修改为如下 随后chmod 777 vnc xstartup 就可以了 教程视屏 xff1a https clou
  • 【云计算学习笔记(二十一)】之Neutron子服务详细介绍

    文章目录 本文章由公号 开发小鸽 发布 xff01 欢迎关注 xff01 xff01 xff01 一 xff0e Neutron xff08 一 xff09 Neutron介绍 xff08 二 xff09 Neutron功能1 二层交换Sw
  • 51单片机、DS18B20、智能窗帘控制系统

    写在最前面 xff1a 文章是我的设计报告搬运过来的 xff0c 嫌排版乱的 xff0c 可以直接下载设计报告 xff0c 包含word xff0c keil工程 xff0c proteus工程 xff01 xff01 xff01 xff0
  • 手写RTOS-PendSV中断

    今天这一篇 xff0c 我们说一下操作系统都要用到的PendSV中断 xff0c 整个操作系统中 xff0c 要自己写的的汇编代码不超过20行 xff0c 全部都在PendSV中断里 以下是 Cotex M3权威指南 里对PendSV的描述
  • 如何知道可执行文件是32-bit还是64-bit

    可以使用GetBinaryType API来获得这个信息 xff1a BOOL GetBinaryType LPCTSTR lpApplicationName LPDWORD lpBinaryType Binary Type可以是下面的值
  • 手写RTOS-使用PendSV进行压栈与出栈操作

    学会使用PendSV中断进行压栈和出栈操作 xff0c 是实现任务调度的关键 今天我们就来学习一下如何使用不超过20行的汇编实现压栈和出栈操作 我们现在来实现这么一个例子 xff1a 先把R4 R11通用寄存器的值保存到一个缓冲区里面 xf
  • 树莓派 Ubuntu mate 16.04 下开启vncserver完整教程

    关于开启vncserver的教程 xff0c 在树莓派上不桶系统上 xff0c 有很多教程 xff0c 杂七杂八 这里的环境是 xff1a 树莓派3b 43 Ubuntu 16 04 mate 一 xff1a 在树莓派下完成以下任务 1 安
  • 在linux上安装oracle数据库并通过远程映射建库

    前期准备 安装虚拟机 xff1a 我这里用的VM15 5 xff0c 可用虚拟机有CentOS以及红帽系列版本下载oracle数据库的安装包以及 rpm补丁包 xff0c 并传入linux虚拟机 xff1a 在虚拟机安装VMTools以共享
  • VMware虚拟机Ubuntu无法连接网络的解决办法

    原文来源 1 Ubuntu网络设置 xff1a 依次单击 System Settings gt Network gt Wired gt Options xff0c 如下图所示 xff1a 依次选择 General xff0c 勾选如下图所示
  • Meta-Learning之How to train your MAML

    这篇文章是MAML的升级版本 xff0c 即MAML 43 43 他针对MAML的一些不足之处做了对应的改进 xff0c 如稳定性 收敛速度 表现力等均得到提升 由于自己的算法实现中有用到MAML xff0c 为了让整体算法有一个好的性能
  • 对于中断的笔记

    我们首先要区分内核与外设 内核是为众多应用程序提供对硬件的安全访问的软件芯片 xff0c 如Cortex M4 xff0c 是ARM公司开发的 外设是ST公司在拿到这个内核后 xff0c 制作了相应的硬件 xff0c 如GPIO RTC以及
  • 正则表达式中?=和?:和?!的理解

    要理解 61 和 xff0c 首先需要理解前瞻 xff0c 后顾 xff0c 负前瞻 xff0c 负后顾四个概念 xff1a 前瞻 xff1a exp1 61 exp2 exp1后边是exp2就匹配 后顾 xff1a lt 61 exp2
  • JAVA中的向上转型和向下转型

    一 向上转型和向下转型 向上转型和向下转型是在JAVA继承操作中用到的东西 xff0c 在讲到转型之前我们需要认识到继承过程中的对象类型转换 xff0c 这种转换有两个特点 xff1a 1 这是继承过程中发生的操作 2 新类是现有类的一种类
  • k8s基于kubeadm部署集群 含集群NotReady解决方案

    1 xff09 简介 kubernetes简称k8s 是用于自动部署 xff0c 扩展和管理容器化应用程序的开源系统 中文官网 xff1a https kubernetes io Zh 中文社区 xff1a https www kubern
  • Ubuntu 下 触摸板不能使用 解决方法

    之前好不容易才安装好双系统 xff0c 今天在Ubuntun下安装东西时 xff0c 电脑没电 xff0c 自己关机了 xff0c 重启后 xff0c 触摸板就不能使用了 xff0c 参看了网上 http blog sina com cn
  • 如何安装touch 1.0.1

    如何安装touch 1 0 1 如何安装touch 1 0 1 如何安装touch 1 0 1 今天需要安装 touch 1 0 1 可能是老版本了吧 xff0c 直接使用pip install touch 61 61 1 0 1不能安装成
  • 立创开源|用立创EDA自制ST-Link V2.1调试器

    分享一个2年前在立创开源硬件平台上开源的项目 xff0c 该项目目前是平台上最热门的ST LINK V2 1项目 xff0c 也是点赞数最多的一个ST LINK相关的项目 xff0c 以下是该项目的累积数据 项目作者 xff1a 攻城狮晨哲
  • 一文带你快速理解FreeRTOS代码规范

    关注 星标嵌入式客栈 xff0c 精彩及时送达 导读 遇到些朋友感觉FreeRTOS内核代码看起来很不习惯 xff0c 不习惯其编码风格 xff0c 本文就来梳理一下其代码规范 xff0c 便于提高阅读其代码的效率 代码基于FreeRTOS

随机推荐

  • 校园网经常掉线解决

    我们要访问一个网站 xff0c 通常是在游览器里输入这个网站的网址 xff0c 然后回车 xff0c 这个时候 xff0c DNS 服务器会自动把它解析成 IP 地址 xff0c 实际上我们是通过 IP 来访问网站的 xff0c 网址只不过
  • RHEL7.4 vnc 黑屏问题

    https access redhat com solutions 3167971 环境 Red Hat Enterprise Linux 7 4tigervnc server 1 8 0 1 el7gnome shell 3 22 3 1
  • CentOS 7 禁止 root 直接登陆 及 修改默认端口

    一 禁止 Root 直接登陆 1 新建登陆用户 useradd tom 添加用户 passwd tom 设置密码 2 修改 sshd 配置文件 vi etc ssh sshd config 修改内容 PermitRootLogin no 改
  • 子网划分总结和技巧

    VLSM可变子网掩码对应CIDR值 下面是C类地址的划分技巧 1 确定划分子网数 子网数 61 2 n xff0c n代表子网掩码往右移动的位数 例如 xff1a 要划分2个子网 xff0c 子网掩码需要往右移动1位 xff0c 2 1 6
  • ubuntu虚拟机可以ping通主机但ssh连不上

    其它配置都正确后 xff0c 命令行执行此命令 xff1a service sshd start xff1b
  • android 安卓手机如何投屏到显示器

    这几天有个需求 xff0c 可以手机投屏到显示器上 xff0c 经过一番研究 xff0c 手机连接电脑再投屏是可以的 xff0c but xff0c 要想直接手机插一根线连接显示器 xff0c 只有支持USB3 0及以上的才可以 xff0c
  • sed之两个文件共有特征行的合并输出

    cat a txt 01 12510101 4001 02 12310001 4002 03 12550101 4003 04 12610001 4004 05 12810001 4005 06 12310001 4006 07 12710
  • Mac中有g++/clang,但引用报错:xcrun: error: invalid active developer path (/Library/Developer/Command...

    问题 xff1a 在Mac上安装fasttext的时候 xff0c 发现g 43 43 或者clang都不能直接引用 于是在终端用which检查了一下 xff0c 发现都是存在于 usr bin 目录中的 which g span clas
  • 利用XRDP远程登陆linux系统

    http linux chinaunix net bbs viewthread php tid 61 1149869 一般情况下我们用ssh客户端远程登陆inux系统 xff0c 至于图形界面下的linux 远程登陆工具 xff0c 我们一
  • BOND 动态链路聚合 lacp配置及相关问题

    内容基本都是参考的 xff0c 哪里有雷同或者错的地方 xff0c 请批评指正 针对802 3ad模式的业务分析 xff1a 场景 xff1a 两个千兆网口 xff0c 聚合成bond0 动态链路聚合抓取数据包格式分析 xff1a 后台bo
  • VNC使用介绍

    VNC在内部网络中经常被大家用到 xff0c 该工具同时具备远程操作和传输文件的双重功能 xff0c 而且速度也是很快的 xff0c xff08 低版本不具备文件传输功能 xff09 深受大众喜爱 xff0c 今天就简单写下在使用VNC的过
  • 清除chrome浏览器缓存

    之前有写过设置缓存 本文解决清除html缓存 如何才能清除缓存呢 xff1f 一下是几个清除浏览器缓存的方法 xff1a 方法1 chrome浏览器地址 xff1a chrome settings clearBrowserData xff1
  • Iterator 接口

    具有原生的Iterator 接口的数据结构有 Array Map Set String TypedArray arguments对象 NodeList对象 面我们来实现将class 和 object 也变成迭代的对象 实现的关键就是 Sym
  • 容器和LXC简单命令

    容器和LXC简单命令 文章目录 容器和LXC简单命令一 CGroup xff08 控制组 xff09 的功能1 cgroup xff08 容器控制组 xff09 1 1 功能 xff1a 1 2 具体功能 xff1a 1 3 控制组可以限制
  • Podman设置容器开机自启

    Podman设置容器开机自启 1 podman管理员容器开机自启动 span class token number 1 span span class token operator span span class token operato
  • Linux中tty、pty、pts的概念区别

    http blog sina com cn s blog 638ac15c01012e0v html 基本概念 xff1a 1 gt tty 终端设备的统称 tty一词源于Teletypes xff0c 或teletypewriters x
  • Linux下vnc的安装、使用以及设置开机启动

    安装和使用VNC resbian系统自带realvnc vnc server 启动vnc服务 vncserver 1 xff08 1类似与端口号 xff0c 也可以理解为桌面序号 xff09 关闭vnc服务 vncserver kill 1
  • 单例模式与双重锁

    设计模式中 xff0c 最为基础与常见的就是单例模式 这也是经常在面试过程中被要求手写的设计模式 下面就先写一个简单的单例 xff1a public class Singleton private static Singleton sing
  • tensorflow安装时成功,但引用时提示:Could not load dynamic library ‘cudart64_101.dll‘…… if you do not have a GPU

    问题 xff1a 前几天tensorflow已经安装成功 xff0c 并顺利引用 但是这几天安装了与之冲突的包 xff1b 在重新调整各个包的版本后 xff0c 引用tensorflow提示出错 xff1a gt gt gt import
  • 【Linux】线程实例 | 简单线程池

    今天来写一个简单版本的线程池 1 啥是线程池 池塘 xff0c 顾名思义 xff0c 线程池就是一个有很多线程的容器 我们只需要把任务交到这个线程的池子里面 xff0c 其就能帮我们多线程执行任务 xff0c 计算出结果 与阻塞队列不同的是