【Linux】:线程池

news2025/3/13 5:43:20

朋友们、伙计们,我们又见面了,本期来给大家带来线程池相关的知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!

C 语 言 专 栏:C语言:从入门到精通

数据结构专栏:数据结构

个  人  主  页 :stackY、

C + + 专 栏   :C++

Linux 专 栏  :Linux

​ 

目录

1. 线程池

1.1 预备工作

1.2 模块设计

2. 基础框架

2.1 测试版接口

2.2.1 Main.cc测试

3. 引入任务

3.1 执行任务(ThreadRun)

3.2 添加任务

3.3 Main.cc测试

4. 接入日志功能

5. 设计单例


1. 线程池

我们之前要用到线程的地方都是当有任务来的时候我们才创建线程,就会导致效率比较低下,所以我们想实现一个线程池,预先提现创建好一批线程,然后在线程池中会有一个任务队列,我们也可以向任务队列中添加任务,然后我们预先创建好的线程就开始依次从任务队列中获取任务并执行,这样子效率就高不少了;

1.1 预备工作

我们的线程池因为是多线程,所以必须要有锁,所以我们先将锁进行简单的封装:

LockGuard.hpp:

#pragma once

#include <pthread.h>

// 不定义锁,默认认为外部会给我们传入锁对象
class Mutex
{
public:
    Mutex(pthread_mutex_t *lock):_lock(lock)
    {}
    void Lock()
    {
        pthread_mutex_lock(_lock);
    }
    void Unlock()
    {
        pthread_mutex_unlock(_lock);
    }
    ~Mutex()
    {}

private:
    pthread_mutex_t *_lock;
};

class LockGuard
{
public:
    LockGuard(pthread_mutex_t *lock): _mutex(lock)
    {
        _mutex.Lock();
    }
    ~LockGuard()
    {
        _mutex.Unlock();
    }
private:
    Mutex _mutex;
};

然后我们线程池也需要用到线程,所以我们使用之前封装好的线程(Thread.hpp),同时我们还想引入一下日志功能(Log.hpp);

关于日志的详细实现请移步至:负载均衡在线OJ里面有对日志的介绍,这里将其复用一下,稍作修改;

1.2 模块设计

我们实现线程池使用分模块编写,每个文件实现对应的功能,让代码不会冗余在一起;

  • ThreadPool.hpp:实现线程池的主要逻辑
  • Thread.hpp:封装原生线程库
  • LockGuard.hpp:封装互斥锁
  • Log.hpp:日志功能
  • Main.cc:对线程池进行测试
  • Makefile:自动化构建代码

2. 基础框架

  • 我们的线程池中需要有一批线程,所以我们使用vector来管理这批线程,还可以设置线程池中需要有多少个线程;
  • 然后需要有一个储存任务的队列、锁、条件变量;
  • 我们使用模版编程来实现自定义任务类型;
  • 初步实现的功能就是一个启动线程池和一个向线程池中push任务。

2.1 测试版接口

  • 我们想有一个运行任务的接口(ThreadRun),然后还想要一个等待线程的接口(Wait);
  • 我们要运行线程池就先需要创建一批线程,这个工作我们在构造函数中实现;
  • 有了线程之后,我们要启动线程就遍历线程池,然后一次调用他们的启动接口即可;
  • 为了先进行测试,我们的ThreadRun接口就先写一段测试代码;
  • 等待线程的接口同样也是遍历然后调用join;

2.2.1 Main.cc测试

这样子已经实现了一个非常简易版的线程池了;

3. 引入任务

上面实现的代码是用来测试基本逻辑的,我们的任务队列中是没有任务的,所以接下来我们需要引入一批任务:

Task.hpp:

#pragma once
#include <iostream>
#include <string>
#include <unistd.h>

const int defaultvalue = 0;

enum
{
    ok = 0,
    div_zero,
    mod_zero,
    unknow
};

const std::string opers = "+-*/%";

class Task
{
public:
    Task()
    {
    }
    Task(int x, int y, char op)
        : data_x(x), data_y(y), oper(op), result(defaultvalue), code(ok)
    {
    }
    void Run()
    {
        switch (oper)
        {
        case '+':
            result = data_x + data_y;
            break;
        case '-':
            result = data_x - data_y;
            break;
        case '*':
            result = data_x * data_y;
            break;
        case '/':
        {
            if (data_y == 0)
                code = div_zero;
            else
                result = data_x / data_y;
        }
        break;
        case '%':
        {
            if (data_y == 0)
                code = mod_zero;
            else
                result = data_x % data_y;
        }
        break;
        default:
            code = unknow;
            break;
        }
    }
    void operator()()
    {
        Run();
    }
    std::string PrintTask()
    {
        std::string s;
        s = std::to_string(data_x);
        s += oper;
        s += std::to_string(data_y);
        s += "=?";
        return s;
    }
    std::string PrintResult()
    {
        std::string s;
        s = std::to_string(data_x);
        s += oper;
        s += std::to_string(data_y);
        s += "=";
        s += std::to_string(result);
        s += " [";
        s += std::to_string(code);
        s += "]";
        return s;
    }
    ~Task()
    {
    }

private:
    int data_x;
    int data_y;
    char oper; // + - * / %

    int result;
    int code; // 结果码,0: 结果可信 !0: 结果不可信,1,2,3,4
};

关于任务设计大家也可以自己设计,不想设计的直接拷贝即可,这里的任务就是对数据进行四种运算;

3.1 执行任务(ThreadRun)

有了任务之后,我们想要执行我们的任务,在执行之前我们想要实现两个接口,一个是任务队列没有任务时,线程就一直等待,另一个是有了任务就将线程进行唤醒并执行任务;

在执行任务接口中首先肯定需要取任务(在任务队列中取任务)如果任务队列中没有任务,就需要进行等待,取到任务之后就将任务分配给线程进行处理任务即可,取任务的这个过程是需要用锁进行保护的;

 

3.2 添加任务

添加任务也是需要用锁保护的,另外将任务添加到任务队列中时需要将线程唤醒去执行任务;

3.3 Main.cc测试

本次测试我们就使用我们引入的任务;

Main.cc:

#include <iostream>
#include <memory>
#include <ctime>

#include "ThreadPool.hpp"
#include "Task.hpp"

int main()
{
    std::unique_ptr<ThreadPool<Task>> tp(new ThreadPool<Task>());
    tp->Start();
    srand((uint64_t)time(nullptr) ^ getpid());

    while (true)
    {
        int x = rand() % 100 + 1;
        usleep(1234);
        int y = rand() % 200;
        usleep(1234);
        char oper = opers[rand() % opers.size()];

        Task t(x, y, oper);
        tp->Push(t);
        // std::cout << "make task: " << t.PrintTask() << std::endl;
        sleep(1);
    }
    tp->Wait();
    return 0;
}

4. 接入日志功能

#pragma once

#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <unistd.h>
#include "Thread.hpp"
#include "LockGuard.hpp"
#include "Log.hpp"
#include "Task.hpp"

using namespace ns_log;
static const int defaultnum = 5;

// 线程数据
class ThreadData
{
public:
    ThreadData(const std::string &name) : threadname(name)
    {
    }
    ~ThreadData()
    {
    }

public:
    std::string threadname;
};

template <class T>
class ThreadPool
{
public:
    ThreadPool(int num = defaultnum) : _thread_num(num)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
        // 创建指定个数的线程
        for (int i = 0; i < _thread_num; i++)
        {
            std::string threadname = "thread-";
            threadname += std::to_string(i + 1);

            ThreadData td(threadname);
            // 创建
            // Thread<ThreadData> t(threadname, std::bind(&ThreadPool<T>::ThreadRun, this, std::placeholders::_1), td);
            // _threads.push_back(t);
            // 两种写法都可行,这种写法可以减少拷贝
            _threads.emplace_back(threadname, std::bind(&ThreadPool<T>::ThreadRun, this, std::placeholders::_1), td);
            LOG(INFO) << td.threadname << " is created..." << "\n";
        }
    }

    // 启动线程池
    bool Start()
    {
        for (auto &thread : _threads)
        {
            thread.Start();
            LOG(INFO) << thread.ThreadName() << " is running..." << "\n";
        }
        return true;
    }
    void ThreadWait() // 等待
    {
        pthread_cond_wait(&_cond, &_mutex);
    }
    void ThreadWakeup() // 唤醒
    {
        pthread_cond_signal(&_cond);
    }
    // 执行任务
    void ThreadRun(ThreadData &td)
    {
        while (true)
        {
            T t;
            // 取任务
            {
                // 加锁
                LockGuard lockguard(&_mutex);
                while (_q.empty())
                {
                    // 没有任务时就等待
                    ThreadWait();
                }
                t = _q.front();
                _q.pop();
            }
            // 处理任务
            t();
            LOG(DEBUG) << td.threadname << " handler task " << t.PrintTask() << " ,result is: " << t.PrintResult() << "\n";
        }
    }
    // 添加任务
    void Push(const T &in)
    {
        // 加锁
        LockGuard lockguard(&_mutex);
        // 添加
        _q.push(in);
        // 唤醒
        ThreadWakeup();
    }
    // 等待线程
    void Wait()
    {
        for (auto &thread : _threads)
        {
            thread.Join();
        }
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }

private:
    ThreadPool(const ThreadPool<T> &tp) = delete;
    const ThreadPool<T> &operator=(const ThreadPool<T>) = delete;
    std::queue<T> _q;                         // 任务队列
    std::vector<Thread<ThreadData>> _threads; // 线程池
    int _thread_num;                          // 线程个数
    pthread_mutex_t _mutex;                   // 锁
    pthread_cond_t _cond;                     // 条件变量
};

5. 设计单例

除了上面实现的功能,我们也可以将我们的线程池设置成为单例模式,这里采用的是懒汉模式;

需要注意的是,因为是多线程,所以在获取单例的时候存在线程安全问题,如果同时有多个线程来获取单例,就会创建多个单例,所以也需要用到互斥锁;

所有源码:https://gitee.com/yue-sir-bit/linux/tree/master/ThreadPool

 

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

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

相关文章

共享内存(System V)——进程通信

个人主页&#xff1a;敲上瘾-CSDN博客 进程通信&#xff1a; 匿名管道&#xff1a;进程池的制作&#xff08;linux进程间通信&#xff0c;匿名管道... ...&#xff09;-CSDN博客命名管道&#xff1a;命名管道——进程间通信-CSDN博客 目录 一、共享内存的原理 二、信道的建立 …

ctfhub-HTTP协议

请求方式 它要我们使用CTF**B Method,其实就是ctfhub方式 我们直接抓包试一试&#xff0c;把GET改成CTFHUB,在发送到repeater 在repeater处点击发送&#xff0c;得到响应 302跳转 点击“give me flag"没有任何变化&#xff0c;我们抓个包试试 我们把它发送到repeater&…

【TMS570LC4357】之工程创建

备注&#xff1a;具体资料请在官网海淘.TMS570LC4357资料 在线文档Hercules Safety MCU Resource Guide — Hercules Safety MCUs Documentation XDS100 Debug Probe (ti.com) Git https://git.ti.com/git/hercules_examples/hercules_examples.git https://git.ti.com/cgit/h…

一种改进的Estimation-of-Distribution差分进化算法

为了充分利用差分进化&#xff08;DE&#xff09;的强大开发和estimation-of-distribution算法&#xff08;EDA&#xff09;的强大探索&#xff0c;提出了一种混合estimation-of-distribution算法的改进差分进化IDE-EDA。首先&#xff0c;提出了一种新的协同进化框架&#xff0…

[数据结构]排序之希尔排序( 缩小增量排序 )

希尔排序法又称缩小增量法。希尔排序法的基本思想是&#xff1a; 先选定一个整数&#xff0c;把待排序文件中所有记录分成个 组&#xff0c;所有距离为的记录分在同一组内&#xff0c;并对每一组内的记录进行排序。然后&#xff0c;取&#xff0c;重复上述分组和排序的工 作。当…

进程(下)【Linux操作系统】

文章目录 进程的状态R状态&#xff1a;S状态&#xff1a;D状态&#xff1a;T状态t状态Z状态&#xff1a;孤儿进程X状态&#xff1a; 进程的优先级如果我们要修改一个进程的优先级重置进程优先级 进程切换进程的调度 进程的状态 在内核中&#xff0c;进程状态的表示&#xff0c…

Insar结合ISCE2,某一个文件进行并行-stackSentinel.py

stackSentinel.py 依次执行 run_01 到 run_15&#xff0c;记录各自的日志 并行执行 run_16 里的所有命令&#xff0c;仍然记录日志 不知道对不对&#xff0c;测试的时间有点长就给停了 #!/bin/bash# ✅ 适用于 WSL/Linux runfiles_path"/mnt/e/insar_order_test/Stack…

2.2.3 TCP—UDP-QUIC

文章目录 2.2.3 TCP—UDP-QUIC1. TCP如何做到可靠性传输1. ACK机制2. 重传机制3. 序号机制4. 窗口机制5. 流量机制6. 带宽机制 2. tcp和udp如何选择1. tcp和udp格式对比2. ARQ协议&#xff08;Automatic Repeat reQuest&#xff0c;自动重传请求&#xff09;1. ARQ协议的主要类…

【Golang】第一弹-----初步认识GO语言

笔上得来终觉浅,绝知此事要躬行 &#x1f525; 个人主页&#xff1a;星云爱编程 &#x1f525; 所属专栏&#xff1a;Golang &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 一、Go语言的简单介绍 1、G…

K8S学习之基础二十三:k8s的持久化存储之nfs

K8S持久化存储之nfs ​ 在 Kubernetes (k8s) 中使用 NFS&#xff08;Network File System&#xff09;作为存储解决方案是一种常见的方式&#xff0c;特别是在需要共享存储的场景中。以下是关于如何在 Kubernetes 中使用 NFS 存储的详细说明&#xff1a; 1. 准备 NFS 服务器 …

【Linux通信篇】深入理解进程间通信——管道

--------------------------------------------------------------------------------------------------------------------------------- 每日鸡汤&#xff1a;找一个对的人&#xff0c;然后好好去爱。一个你跟他在一起&#xff0c;然后又可以舒舒服服做自己的人。 -------…

Redis--Set类型

目录 一、引言 二、介绍 三、命令 1.sadd,smembers,sismember 2.spop&#xff0c;srandmember 3.smove&#xff0c;srem 4.sinter&#xff0c;sinterstore 5.sunion,sunionstore,sdiff,sdiffstore 四、内部编码 1.intset 2.hashtable 五、应用场景 1.使用Set保存用…

【0013】Python数据类型-列表类型详解

如果你觉得我的文章写的不错&#xff0c;请关注我哟&#xff0c;请点赞、评论&#xff0c;收藏此文章&#xff0c;谢谢&#xff01; 本文内容体系结构如下&#xff1a; Python列表&#xff0c;作为编程中的基础数据结构&#xff0c;扮演着至关重要的角色。它不仅能够存储一系…

文件上传靶场(10--20)

目录 实验环境&#xff1a; 具体内容实现&#xff1a; 第十关&#xff08;双写绕过&#xff09;&#xff1a; 第十一关&#xff1a;&#xff08;%00截断&#xff0c;此漏洞在5.2版本中&#xff09; 正确用法 错误用法 思路&#xff1a; 操作过程&#xff1a; 第十二关…

【前端】BOM DOM

两天更新完毕&#xff0c;建议关注收藏点赞 友情链接&#xff1a; HTML&CSS&LESS&Bootstrap&Emmet Axios & AJAX & Fetch BOM DOM 待整理 js2 Web API 是浏览器提供的一套操作浏览器功能和页面元素的 API ( BOM 和 DOM)。官方文档点击跳转 目录 BOMDOM…

计算机网络——IP、MAC、ARP

一、IP地址 1. 什么是IP地址&#xff1f; IP地址&#xff08;Internet Protocol Address&#xff09;是互联网中设备的唯一逻辑标识符&#xff0c;类似于现实生活中的“门牌号”。它分为 IPv4&#xff08;32位&#xff0c;如 192.168.1.1&#xff09;和 IPv6&#xff08;128位…

代码优化——基于element-plus封装组件:表单封装

前言 今天实现一个基于element-plus表单组件的二次封装&#xff0c;什么是二次封装&#xff1f;查看以下表单&#xff0c;传统表单组件是不是用<el-form>嵌套几个<el-form-item>即可实现&#xff0c;那么一个表单可不可以实现&#xff0c;传入一个对象给封装组件&a…

C/C++中使用CopyFile、CopyFileEx原理、用法、区别及分别在哪些场景使用

文章目录 1. CopyFile原理函数原型返回值用法示例适用场景 2. CopyFileEx原理函数原型返回值用法示例适用场景 3. 核心区别4. 选择建议5. 常见问题6.区别 在Windows系统编程中&#xff0c;CopyFile和CopyFileEx是用于文件复制的两个API函数。它们的核心区别在于功能扩展性和控制…

qt 多进程使用共享内存 ,加速数据读写,进程间通信 共享内存

Summary: 项目中我们有时需要使用共享内存共享数据&#xff0c;这样&#xff0c;数据不用进程IO读写&#xff0c;加进数据加载和落地&#xff1b; 程序退出时&#xff0c;再保存到本地&#xff1b;速度提升数十倍&#xff1b; Part1:QSharedMemory Windows平台下进程间通信…

【鸿蒙开发】OpenHarmony调测工具hdc使用教程(设备开发者)

00. 目录 文章目录 00. 目录01. OpenHarmony概述02. hdc简介03. hdc获取04. option相关的命令05. 查询设备列表的命令06. 服务进程相关命令07. 网络相关的命令08. 文件相关的命令09. 应用相关的命令10. 调试相关的命令11. 常见问题12. 附录 01. OpenHarmony概述 OpenHarmony是…