【Linux 23】线程池

news2024/12/24 11:38:57

文章目录

  • 🌈 一、线程池的概念
  • 🌈 二、线程池的应用场景
  • 🌈 三、线程池的实现

🌈 一、线程池的概念

  • 线程池 (thread pool) 是一种利用池化技术的线程使用模式

  • 虽然创建线程的代价比创建进程的要小很多,但小并不意味着没有。如果每次处理任务都要创建线程,积少成多下,代价还是蛮高的。

  • 而线程池就可以解决这种问题,提前先创建出一批线程,没任务时,让这些线程泡在池子里睡觉,来任务后,将这些线程从池子里叫起来干活。

    • 水池里流淌的是水流,而线程池里流淌的则是执行流 (线程)。
  • 不要被线程池这个称呼给套住了,本质上来说,线程池 = 任务队列 + 条件变量 + 多执行流

    • 当任务队列中没任务时,让一堆线程在指定条件变量下等待;
    • 当任务队列中有任务时,会唤醒在条件变量下等待的线程,让被唤醒的线程从任务队列中获取任务。
  • 说到底,线程池本质上就是个生产消费模型

image-20240923163036559

🌈 二、线程池的应用场景

  1. 需要大量的线程来完成任务,且完成任务的时间比较短。
    • 例如 web 服务器完成网页请求,这种单个任务小,但任务量大的任务,就很适合使用线程池技术。
  2. 对性能要求苛刻的应用,要求服务器迅速响应客户请求。
  3. 接收突发性的大量请求,但不至于使服务器因此产生大量线程的应用。
    • 在没有线程池的情况下,如果没有线程池,短时间内将产生大量线程,可能使内存到达极限。

🌈 三、线程池的实现

  • 当前要实现一个主线程不停的往任务队列中放任务,然后让线程池中的线程从任务队列中获取任务,再进行任务处理的一个简易线程池。

  • 实现的这个简易的线程池整体分为线程池部分代码、任务类型部分代码、主线程部分代码这三部分。

  • 这三部分代码可以分别写在三个文件中 (记得包含对应头文件),也可以放在一个文件中,此处为了方便演示就全写在一个文件中。

#include <queue>
#include <iostream>
#include <unistd.h>
#include <pthread.h>

using std::cerr;
using std::cout;
using std::endl;
using std::queue;

/* ---------- 任务类型代码设计 ---------- */
class task
{
private:
    int _x;   // 左操作数
    int _y;   // 右操作数
    char _op; // 操作符

public:
    task(int x = 0, int y = 0, char op = 0)
        : _x(x), _y(y), _op(op)
    {}

    // 处理任务的方法
    void run()
    {
        int result = 0; // 记录计算结果

        switch (_op)
        {
        case '+':
            result = _x + _y;
            break;
        case '-':
            result = _x - _y;
            break;
        case '*':
            result = _x * _y;
            break;
        case '/':
            if (_y == 0)
            {
                cerr << "除零错误" << std::endl;
                return;
            }
            else
            {
                result = _x / _y;
            }
            break;
        case '%':
            if (_y == 0)
            {
                std::cerr << "模零错误" << std::endl;
                return;
            }
            else
            {
                result = _x % _y;
            }
            break;
        default:
            cerr << "非法运算符" << endl;
            return;
        }

        cout << "新线程 [" << pthread_self() << "] 获取任务成功, 任务的处理结果为: "
             << _x << " " << _op << " " << _y << " = " << result << endl;
    }

    ~task()
    {}
};

/* ---------- 线程池代码设计 ---------- */
#define NUM 5               // 任务队列的容量

template <typename T>
class thread_pool
{
private:
    queue<T> _task_queue;   // 任务队列
    int _thread_num;        // 线程池中线程的数量
    pthread_mutex_t _mutex; // 线程池中的线程的互斥锁
    pthread_cond_t _cond;   // 线程池中的线程的条件变量

private:
    // 判断任务队列是否为空
    bool is_empty()
    {
        return _task_queue.size() == 0;
    }

    // 上锁
    void lock_queue()
    {
        pthread_mutex_lock(&_mutex);
    }

    // 解锁
    void unlock_queue()
    {
        pthread_mutex_unlock(&_mutex);
    }

    // 让线程去指定条件变量处等待等待
    void wait()
    {
        pthread_cond_wait(&_cond, &_mutex);
    }

    // 唤醒在指定条件变量处等待的线程
    void wakeup()
    {
        pthread_cond_signal(&_cond);
    }

public:
    // 线程池的构造函数
    thread_pool(int num = NUM)
        : _thread_num(num)
    {
        pthread_mutex_init(&_mutex, nullptr); // 初始化互斥锁
        pthread_cond_init(&_cond, nullptr);   // 初始化条件变量
    }

    // 线程池中的线程所要执行的函数
    static void *routine(void *arg)
    {
        pthread_detach(pthread_self());
        thread_pool *self = (thread_pool *)arg;

        // 不断从任务队列获取中任务,然后处理这些任务
        while (true)
        {
            self->lock_queue();     	// 获取任务前, 先对任务队列上锁
            while (self->is_empty())	// 如果任务队列为空, 则让线程去休眠
                self->wait();
            T task;               		// 定义任务对象
            self->pop(&task);     		// 用定义好的任务对象从任务队列中获取任务
            self->unlock_queue(); 		// 获取任务后, 要对任务队列解锁
            task.run();           		// 处理获取到的任务
        }
    }

    // 初始化线程池 (为线程池创建一批线程)
    void thread_pool_init()
    {
        pthread_t tid;

        for (int i = 0; i < _thread_num; i++)
            pthread_create(&tid, nullptr, routine, this); // 传入 this 指针作为 routine 函数的参数
    }

    // 往任务队列放任务(主线程调用)
    void push(const T &task)
    {
        lock_queue();               // 给任务队列上锁
        _task_queue.push(task);     // 往任务队列中放入任务
        unlock_queue();             // 为任务队列解锁
        wakeup();                   // 来活了, 唤醒在线程池中休眠的线程
    }

    // 从任务队列获取任务(线程池中的线程调用)
    void pop(T *task)
    {
        *task = _task_queue.front();
        _task_queue.pop();
    }

    // 线程池的析构函数
    ~thread_pool()
    {
        pthread_mutex_destroy(&_mutex); // 销毁互斥锁
        pthread_cond_destroy(&_cond);   // 销毁条件变量
    }
};

/* ---------- 主线程代码设计 ---------- */
int main()
{
    srand((unsigned int)time(nullptr)); // 随机数种子

    thread_pool<task> *tp = new thread_pool<task>; // 定义线程池对象
    tp->thread_pool_init();                        // 初始化线程池当中的线程
    const char *op = "+-*/%";                      // 操作符集

    // 不断往任务队列塞计算任务
    while (true)                        
    {
        sleep(1);

        int x = rand() % 100;           // 左操作数
        int y = rand() % 100;           // 右操作数
        int index = rand() % 5;         // 随机获取操作符集中的某个操作符的下标

        task task(x, y, op[index]);     // 构造一个任务对象
        tp->push(task);                 // 将构造的任务对象放入任务队列

        cout << "主线程放入任务完毕, 放入的任务为: "
             << x << " " << op[index] << " " << y << " = ?" << endl;
    }

    return 0;
}

  • 因为没有将屏幕这个临界资源也用锁保护起来,因此才会有一开始的主线程和新线程打印的内容混在一起的状况出现。
  • 要解决这个问题,可以直接使用互斥锁将访问屏幕这个临界资源的临界区保护起来就行。

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

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

相关文章

Mysql高级篇(下)——日志

日志 一、日志概述二、日志弊端二、日志分类三、 各日志详情介绍1、慢查询日志&#xff08;Slow Query Log&#xff09;2、通用查询日志&#xff08;General Query Log&#xff09;3、错误日志&#xff08;Error Log&#xff09;4、二进制日志&#xff08;Binary Log&#xff0…

初识Linux · 进程等待

目录 前言&#xff1a; 进程等待是什么 为什么需要进程等待 进程等待都在做什么 前言&#xff1a; 通过上文的学习&#xff0c;我们了解了进程终止&#xff0c;知道终止是在干什么&#xff0c;终止的三种情况&#xff0c;以及有了退出码&#xff0c;错误码的概念&#xff…

基于大数据的学生体质健康信息系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

图像数据增强albumentations之自然景色

一 背景 最近在做关于图像数据增强方面&#xff0c;发现albumentations这个包比较好用&#xff0c;在此学习一下如何使用API二 albumentations 安装 注意&#xff0c;注意&#xff0c;注意 python版本3.8 pip install -U albumentations三 API学习 1 模拟雨水 import os i…

慢病中医药膳养生食疗管理微信小程序、基于微信小程序的慢病中医药膳养生食疗管理系统设计与实现、中医药膳养生食疗管理微信小程序的开发与应用(源码+文档+定制)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

【SpringCloud】注册中⼼的其他实现-Nacos

Nacos 1. Nacos简介 2. Nacos安装2.1 下载安装包2.2 Windows2.2.1 解压2.2.2 修改单机模式2.2.3 启动Nacos2.2.4 常⻅问题集群模式启动端⼝号冲突 2.3 Linux2.3.1 准备安装包2.3.2 单机模式启动 1. Nacos简介 2018年6⽉, Eureka 2.0宣布闭源(但是1.X版本仍然为活跃项⽬), 同年…

【mmengine】配置器(config)(进阶)继承与导出,命令行修改配置

一、配置文件的继承 1.1 继承机制概述 新建optimizer_cfg.py: optimizer dict(typeSGD, lr0.02, momentum0.9, weight_decay0.0001)新建runtime_cfg.py: device "cuda" gpu_ids [0, 1] batch_size 64 epochs 100 num_workers 8新建resnet50.py: _base_ […

图解C#高级教程(三):泛型

本讲用许多代码示例介绍了 C# 语言当中的泛型&#xff0c;主要包括泛型类、接口、结构、委托和方法。 文章目录 1. 为什么需要泛型&#xff1f;2. 泛型类的定义2.1 泛型类的定义2.2 使用泛型类创建变量和实例 3. 使用泛型类实现一个简单的栈3.1 类型参数的约束3.2 Where 子句3…

不相同的二叉搜索树

给你一个整数 n &#xff0c;求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种&#xff1f;返回满足题意的二叉搜索树的种数。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;5示例 2&#xff1a; 输入&#xff1a;n 1 输出&#xff1a;1提…

数字教学时代:构建高效在线帮助中心的重要性

在数字化教学日益普及的今天&#xff0c;教育领域正经历着前所未有的变革。随着在线课程、虚拟教室、智能学习平台等数字化工具的广泛应用&#xff0c;教育资源的获取方式和学习模式发生了深刻变化。然而&#xff0c;这种变革也带来了新的挑战&#xff0c;其中之一便是如何确保…

YashanDB Docker镜像制作

本文作者&#xff1a;YashanDB中级服务工程师鲍健昕 为什么需要Docker部署数据库 常规使用 yasboot 部署数据库的方法&#xff0c;操作流程复杂&#xff0c;需要配置许多配置文件以及环境变量&#xff0c;不同用户使用的环境不同&#xff0c;那么环境配置也会存在差异&#x…

YOLO11震撼发布!

非常高兴地向大家介绍 Ultralytics YOLO系列的新模型&#xff1a; YOLO11&#xff01; YOLO11 在以往 YOLO 模型基础上带来了一系列强大的功能和优化&#xff0c;使其速度更快、更准确、用途更广泛。主要改进包括 增强了特征提取功能&#xff0c;从而可以更精确地捕捉细节以更…

啤酒在文学中的浪漫形象:精酿啤酒的诗意之旅

在文学的浩瀚星空中&#xff0c;啤酒并非仅仅是醉人的琼浆&#xff0c;它更是一种情感的载体&#xff0c;一种浪漫的符号。尤其是当提及Fendi Club精酿啤酒时&#xff0c;我们仿佛能闻到那从古老酒窖中飘出的馥郁香气&#xff0c;感受到它在文字间流淌的诗意与温情。 一、啤酒…

uniapp中检测应用更新的两种方式-升级中心之uni-upgrade-center-app

uniapp一个很是用的功能&#xff0c;就是在我们发布新版本的app后&#xff0c;需要提示用户进行app更新&#xff0c;并告知用户我们新版的app更新信息&#xff0c;以使得用户能及时使用上我们新开发的功能&#xff0c;提升用户的实用度和粘性。注意:这个功能只能在app端使用 效…

损失函数篇 | YOLOv10 更换损失函数之 MPDIoU | 《2023 一种用于高效准确的边界框回归的损失函数》

论文地址:https://arxiv.org/pdf/2307.07662v1.pdf 边界框回归(Bounding Box Regression,BBR)在目标检测和实例分割中得到了广泛应用,是目标定位的重要步骤。然而,对于边界框回归的大多数现有损失函数来说,当预测的边界框与真值边界框具有相同的长宽比,但宽度和高度的…

信号量SEM

前提 1.信号量的本质是一把计数器 2.申请信号本质就是预订资源 3.PV操作是原子的! 将一个公共资源当做整体访问-->锁 如果公共资源不当做整体使用&#xff0c;多进程可以并发的访问公共资源&#xff0c;但不是同一个区域&#xff0c;为了将资源均分&#xff0c;所以有了…

如何利用ChatGPT开发一个盈利的AI写作助手网站

3-1 整体介绍写作助手及原型展示说明 在当今数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;技术正逐步改变我们的生活方式&#xff0c;特别是在内容创作领域。本文将详细介绍如何利用ChatGPT技术&#xff0c;开发一个能够生成高质量内容的AI写作助手网站&#xff…

埃及 Explained

古埃及&#xff0c;位于尼罗河畔的神秘文明&#xff0c;曾在北非的荒漠中繁荣昌盛。这个充满谜团的王国凭借其宏伟的成就和神秘的文化&#xff0c;数百年来吸引了无数人的好奇心。 埃及人创造了复杂的象形文字&#xff0c;建造了像吉萨大金字塔这样宏伟的建筑&#xff0c;并通…

字体文件压缩

技术点 npm、html、font-spider 实现原理 个人理解&#xff1a;先引入原先字体&#xff0c;然后重置字符为空&#xff0c;根据你自己填充文字、字符等重新生成字体文件&#xff0c;因此在引入的时候务必添加自己使用的文字、字符等&#xff01;&#xff01;&#xff01; 实…

k8s高级功能(系统升级)

版本升级 k8s由于1.23 到1.24底层变了&#xff0c;所以本次示例以1.22升到1.23 升级Master节点 &#xff08;在master节点执行&#xff09; 腾空节点 kubectl drain master --ignore-daemonsets 升级kubeadm yum install -y kubelet-1.23.17 kubeadm-1.23.17 kubectl-1.23.17…