Linux线程池

news2024/11/27 15:45:27

文章目录:

  • 线程池了解
  • 线程池模拟实现

线程池了解

线程池是一种常见的线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务,以避免在处理短时间任务时频繁地创建和销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度,提高程序性能。

线程池通常由以下组件组成:

  • 任务队列:用于存储待执行的任务。
  • 工作线程:线程池维护的多个线程,等待着监督管理者分配可并发执行的任务。
  • 监督管理者:负责管理线程池的工作线程,分配任务给空闲的线程执行,并监控工作线程的运行状态。

线程池的应用场景:

1️⃣ 需要大量的线程来完成任务,且完成任务的时间比较短。WEB 服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,可以想象一个热门网站的点击次数。但对于长时间的任务,比如一个 Telnet 连接请求,线程池的优点就不明显了。因为 Telnet 会话时间比线程的创建时间大多了。

2️⃣ 对性能要求苛刻的应用,比如要求服务器迅速相应客户请求。

3️⃣ 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将输出大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。

4️⃣ 需要限制系统钟同时运行线程的数量,以避免系统资源被耗尽或因过多线程而导致系统性能能下降。

5️⃣ 需要异步执行任务,但又不想为每个任务创建一个新线程,因为这会消耗大量系统资源。

6️⃣ 需要在多个任务之间共享线程池之间共享线程池中的资源,例如共享数据库连接或共享线程安全的数据结构。

线程池的优点:

  • 线程池避免了在处理短时间任务时创建于销毁线程的代价。
  • 线程池不仅能够保证内核充分利用,还能防止过分调度。

注意:线程池中的可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络 sockets 等的数量。

线程池模拟实现

下面我们实现一个简单的线程池,线程池中提供一个任务队列,用于存储给出的任务,以及多个线程。

  1. 创建固定数量的线程池,循环从任务队列中获取任务对象。
  2. 获取到任务对象后,执行任务对象中的任务接口。

下面线程池中引入的任务 task.hpp 实现如下:

#pragma once

#include <iostream>
#include <string>

class Task
{
public:
    Task(int one = 0, int two = 0, char op = '0')
        : elemOne_(one), elemTwo_(two), operator_(op)
    {
    }

    int operator()() { return run(); }

    int run()
    {
        int result = 0;
        switch (operator_)
        {
        case '+':
            result = elemOne_ + elemTwo_;
            break;
        case '-':
            result = elemOne_ - elemTwo_;
            break;
        case '*':
            result = elemOne_ * elemTwo_;
            break;
        case '/':
            if (elemTwo_ == 0)
            {
                std::cout << "div zero,about " << std::endl;
                result = -1;
            }
            else
                result = elemOne_ / elemTwo_;
            break;
        case '%':
            if (elemTwo_ == 0)
            {
                std::cout << "mod zero,about " << std::endl;
                result = -1;
            }
            else
                result = elemOne_ % elemTwo_;
            break;
        default:
            std::cout << "非法操作:" << operator_ << std::endl;
            break;
        }
        return result;
    }

    int get(int *e1, int *e2, char *op)
    {
        *e1 = elemOne_;
        *e2 = elemTwo_;
        *op = operator_;
    }

private:
    int elemOne_;
    int elemTwo_;
    char operator_;
};

线程池代码实现如下:

#pragma once
#include <iostream>
#include <queue>
#include <unistd.h>
#include <cstdlib>
#include <memory>
#include <pthread.h>
#include <cassert>
#include <sys/prctl.h>
using namespace std;

int gThreadNum = 5; // 默认线程数

template <class T>
class ThreadPool
{
    // 加锁和解锁互斥锁
    void lockQueue() { pthread_mutex_lock(&mutex_); }
    void unlockQueue() { pthread_mutex_unlock(&mutex_); }
    // 检测队列是否为空
    bool isEmpty() { return taskQueue_.empty(); }
    // 等待任务队列中有新任务加入
    void waitForTask() { pthread_cond_wait(&cond_, &mutex_); }
    // 唤醒队列中的线程来处理任务
    void choiceThreadForHandler() { pthread_cond_signal(&cond_); }

public:
    // 构造函数,需要传入线程数
    ThreadPool(int threadNum = gThreadNum) : isStart_(false), threadNum_(threadNum)
    {
        assert(threadNum_ > 0);
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&cond_, nullptr);
    }

    // 析构函数,销毁互斥锁和条件变量
    ~ThreadPool()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);
    }

    // 线程执行函数,传入this指针
    static void *threadRoutine(void *args)
    {
        pthread_detach(pthread_self());                         // 线程分离
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args); // 获取线程池对象
        prctl(PR_SET_NAME, "follower");                         // 设置线程名
        while (1)
        {
            tp->lockQueue();      // 加锁
            while (tp->isEmpty()) // 如果任务队列为空,则等待
            {
                tp->waitForTask();
            }
            T t = tp->pop();   // 取出任务
            tp->unlockQueue(); // 解锁

            // for debug
            int one, two;
            char oper;
            t.get(&one, &two, &oper);
            // 执行任务并输出结果
            Log() << "完成计算任务:" << one << oper << two << "=" << t.run() << endl;
        }
    }

    // 从任务队列中取出任务
    T pop()
    {
        T temp = taskQueue_.front();
        taskQueue_.pop();
        return temp;
    }

    // 启动线程池
    void start()
    {
        assert(!isStart_); // 确保线程池未启动
        for (int i = 0; i < threadNum_; i++)
        {
            pthread_t temp;
            pthread_create(&temp, nullptr, threadRoutine, this);
        }
        isStart_ = true;
    }

    // 添加任务到任务队列中
    void push(const T &in)
    {
        lockQueue();
        taskQueue_.push(in);
        choiceThreadForHandler();
        unlockQueue();
    }
private:
    bool isStart_;          // 判断线程池是否启动
    int threadNum_;         // 线程数量
    queue<T> taskQueue_;    // 任务队列,存储待执行的任务
    pthread_mutex_t mutex_; // 互斥锁,保证线程安全
    pthread_cond_t cond_;   // 条件变量,用于线程同步
};

线程池实现说明:

  • 创建了一个模板类 ThreadPool ,该类中包含任务队列、互斥锁、条件变量等成员变量,以及相应的成员函数,用于添加任务、取出任务、启动线程池等操作。
  • 任务队列是被多个线程共享的临界资源,为了保证多个线程对任务队列的安全访问,需要引入互斥锁来保证同一时间只有一个线程可以对任务队列进行操作。
  • 线程池中的线程是通过不断地从任务队列中取出任务来进行处理的。当任务队列为空时,线程需要等待新的任务加入。为了实现该等待操作,需要引入条件变量来实现线程的同步。当任务队列为空时,线程将会等待条件变量,一旦有新的任务加入队列,就被通过条件变量被唤醒,从而继续执行任务。
  • 在线程执行函数中使用 while 替代 if 进行条件判断,当线程被唤醒后,应该再次检测条件是否满足。因为线程可能会出现伪唤醒的情况,即线程在没有明确被唤醒的情况下醒来。因此,这里使用 while 来检测条件是否满足。
  • 在获取任务后,应该尽快的释放锁,然后再处理任务,这样可以最大程度的减少临界区的持有时间,允许其它线程有机会执行并发操作,提高整体效率。
pthread_mutex_lock(&mutex);
// 获取任务
pthread_mutex_unlock(&mutex);
// 处理任务
  • 需要将线程例程(threadRoutine)设置为静态方法。这是因为 pthread_create 函数的第三个参数需要的是一个全局函数,它不能接受非静态成员函数。因为非静态成员函数默认第一个参数是 this ,而 pthread_create 创建的线程函数只能接受一个 void* 参数,这两者是不匹配的,因此,我们需要将其设置为静态的,然后显示地传递线程池对象作为参数。
    在这里插入图片描述

接下来对实现的线程池进行测试,测试代码如下:

#include "ThreadPool.hpp"
#include "task.hpp"
#include <ctime>

int main()
{
    prctl(PR_SET_NAME,"master");

    const string operators = "+-*/%";
    unique_ptr<ThreadPool<Task>> tp(new ThreadPool<Task>()); 
    tp->start();

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

当程序运行起来之后,该进程中有一个 master 主线程,和5个 follower 子线程进行处理任务。

在这里插入图片描述

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

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

相关文章

Python爬虫过程中DNS解析错误解决策略

在Python爬虫开发中&#xff0c;经常会遇到DNS解析错误&#xff0c;这是一个常见且也令人头疼的问题。DNS解析错误可能会导致爬虫失败&#xff0c;但幸运的是&#xff0c;我们可以采取一些策略来处理这些错误&#xff0c;确保爬虫能够正常运行。本文将介绍什么是DNS解析错误&am…

JLMR Micro Super Resolution Algorithm国产微超分算法DEMO

一、简介 目前&#xff0c;做超分算法基本还是以AI训练为主&#xff0c;但是AI基本上都是基于既定场景的训练。而传统的算法基本上都是利用上下文的纹理预测、插值等方案&#xff0c;在图像放大过程中会出现模糊&#xff0c;或马赛克等现象。 我们基于加权概率模型&#xff0c…

后端接口性能优化分析-1

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码&#x1f525;如果感觉博主的文章还不错的话&#xff0c;请&#x1f44d;三连支持&…

SQLite3 数据库学习(一):数据库和 SQLite 基础

参考引用 SQL 必知必会SQLite 权威指南&#xff08;第二版&#xff09;关系型数据库概述 1. 数据库基础 1.1 什么是数据库 数据库&#xff08;database&#xff09;&#xff1a;保存有组织的数据的容器&#xff08;通常是一个文件或一组文件&#xff09; 可以将其想象为一个文…

谷歌提出AGI的6大原则,和5大能力等级

随着ChatGPT等大模型的出现,AGI概念正在从哲学层面快速转向实际应用落地&#xff0c;并且ChatGPT已经展示出了初级AGI的功能&#xff08;如AutoGPT&#xff09;,有不少专家认为&#xff0c;AGI时代可能在10年内到来。 因此&#xff0c;需要一个明确的技术框架来讨论和衡量不同…

图片转excel的三种方案(电脑、手机)

图片怎么转换成excel文件呢?用金鸣表格文字识别是最便捷、最佳的解决方案。也许有些同学会问,那我用手工也可以解决呀,干吗要用软件?这么想就不对了,手工做不但要做表格线,还要手工打字,非常麻烦,而且容易出错,特别是对于数字多的图片,更是要命,现在有金鸣识别就不用那么麻烦…

python读取excel,进行数据处理

一、准备python编译器 二、下载 pyexcel 库 pip install pyexcel-xls三、进行编码读取数据 import pyexcel# 读取Excel文件 成本中心字典 data pyexcel.get_array(file_name成本中心.xls)def hand():#打印数据#print(data)url f"INSERT INTO dst_base.sys_dict(p_…

efcore反向共工程,单元测试

1.安装efcore需要的nuget <PackageReference Include"Microsoft.EntityFrameworkCore" Version"6.0.24" /> <PackageReference Include"Microsoft.EntityFrameworkCore.SqlServer" Version"6.0.24" /> <PackageRefere…

评论:AlexNet和CaffeNet有何区别?

一、说明 在这个故事中&#xff0c;我们回顾了AlexNet和CaffeNet。AlexNet 是2012 年ILSVRC&#xff08;ImageNet 大规模视觉识别竞赛&#xff09;的获胜者&#xff0c;这是一项图像分类竞赛。而CaffeNet是AlexNet的单GPU版&#xff0c;因此&#xff0c;我们平时在普通电脑的Al…

Fortinet 聚焦核心业务增长领域,巩固网安市场领导地位,持续推动行业创新

近日&#xff0c;专注于推动网络与安全融合的全球网络安全领导者 Fortinet&#xff08;NASDAQ&#xff1a;FTNT&#xff09;发布第三季度财报。同期&#xff0c;Fortinet做出重大战略宣布&#xff0c;未来将重点聚焦高速增长的差异化市场。Fortinet 将紧紧围绕安全组网、Univer…

中馥集团双11当日发货销售额突破1000万!

昨日&#xff0c;中馥集团双十一当日发货销售额突破1000万&#xff0c;再创新高&#xff01;双十一大促期间&#xff0c;中馥集团全体上下通力合作&#xff0c;每场直播商品经层层筛选、严格评选的“名品”&#xff0c;既有优质精品文化酒&#xff0c;也有市场火爆的高端酱酒&a…

【java学习—十四】Class类(2)

文章目录 1. Class类2. Class类的常用方法3. 实例化Class类对象&#xff08;四种方法&#xff09; 1. Class类 在 Object 类中定义了以下的方法&#xff0c;此方法将被所有子类继承&#xff1a; public final Class getClass() 以上的方法返回值的类型是一个 Class 类&#xf…

vue2中使用Markdown编辑器

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

Linux必备基础命令,JAVA程序员必备

目录 一、了解基本的左侧栏什么意思​编辑 二、ls&#xff0c;ll&#xff08;list&#xff0c;查找目录内容) 三、cd(change directory&#xff0c;切换目录) 小技巧&#xff0c;我们在查找东西的时候&#xff0c;可以使用tab进行智能补全。 四、touch&#xff08;建立文件…

【运维】-- 在线网络工具

1、https://ping.pe/ 一个免费的在线网络工具&#xff0c;可以帮助您检测和分析IP地址的连接情况。 这是搬瓦工官方做的一个 ping 在线测试网站工具。比较适合测试短时间的 ping 统计&#xff0c;并且在网页上以图表形式统计显示出来。 PS&#xff1a; a、丢包会以红色显示出…

安卓:打包apk时出现Execution failed for task ‘:app:lintVitalRelease

Execution failed for task :lintVitalRelease 程序可以正常运行&#xff0c;但是打包apk的时候报Execution failed for task ‘:app:lintVitalRelease导致打包失败&#xff0c;原因是执行lintVitalRelease失败了&#xff0c;存在错误。解决办法&#xff1a;在app模块的build.…

R程序 示例4.3.2版本包 在centos进行编译部署

为了在CentOS上下载和编译R语言4.3.2包&#xff0c;可以按照以下步骤进行操作&#xff1a; 1.首先&#xff0c;需要安装一些必要的依赖项。可以使用以下命令安装它们&#xff1a; sudo yum install -y epel-release sudo yum install -y gcc gcc-c gcc-gfortran readline-dev…

任意注册漏洞

目录 一漏洞介绍 二实战演示 三漏洞修复 本文由掌控安全学院 - 小博 投稿 一漏洞介绍 1.未验证邮箱/手机号 情景&#xff1a;应用为了方便用户记录用户名&#xff0c;使用邮箱和手机号作为用户名&#xff08;因此很多应用在注册的时候就要求用户填写&#xff0c;多数时候…

gpt-4-vision-preview 识图

这些图片都是流行动画角色的插图。 第一张图片中的角色是一块穿着棕色方形裤子、红领带和白色衬衫的海绵&#xff0c;它站立着并露出开心的笑容。该角色在一个蓝色的背景前&#xff0c;显得非常兴奋和活泼。 第二张图片展示的是一只灰色的小老鼠&#xff0c;表情开心&#xf…