Linux —— 线程池

目录

1.认识线程池

什么是线程池

线程池的优点

线程池的使用场景

2.线程池的实现

线程池的组成

线程池之间的关系

线程池的实现代码

任务类型

线程池类型

主程序


1.认识线程池

什么是线程池

线程池(Thread Pool)是线程的一种使用方式。它通过预先创建一组线程并管理它们的生命周期,来提高多线程应用程序的性能和资源利用率。线程池的核心思想是减少在创建和销毁线程时所产生的开销

线程池的优点

  • 减少开销:线程的创建和销毁是有开销的,线程池通过复用线程减少了这种开销。

  • 提高响应速度:当任务到达时,线程池中已经有现成的线程可以立即执行任务,而不需要等待线程创建。

  • 提高线程的可控性:线程池可以统一管理线程的生命周期、数量和优先级,使得多线程编程更加可控。

  • 防止资源耗尽:通过限制线程的数量,线程池可以防止系统因创建过多线程而耗尽资源。

线程池的使用场景

  1. Web 服务器处理请求:Web 服务器需要同时处理大量客户端请求,每个请求通常是一个短期任务(如 HTTP 请求)。使用线程池可以避免为每个请求创建和销毁线程的开销,提高服务器的响应速度和资源利用率。

  2. **数据库连接池:**数据库操作通常是 I/O 密集型任务,频繁创建和销毁数据库连接会消耗大量资源。通过线程池管理数据库连接,可以复用连接,减少创建和销毁连接的开销。

  3. **异步任务处理:**某些任务不需要立即执行,可以异步处理(如日志记录、邮件发送、消息推送等)。将任务提交到线程池中异步执行,避免阻塞主线程。

  4. 批量数据处理:需要对大量数据进行处理(如文件解析、数据清洗、批量计算等),这些任务可以并行执行。将数据分片后提交到线程池中并行处理,提高处理效率。

关于线程池的应用还有很多,笔者就不一 一赘述了。

2.线程池的实现

线程池的组成

线程池内部主要有一个任务队列 用于存储任务,还有一批线程 从任务队列中获取任务并执行。任务队列中的任务通过一个控制线程 传递进来,并且控制线程还要管理好线程池的生命周期,但是控制线程并不属于线程池

线程池之间的关系

线程池代码中的角色有主线程 和线程池中的从线程 ,主线程只有一个,从线程有多个,主线程往任务队列中放数据,从线程从任务队列中获取数据;此时的**任务队列就是主线程和从线程之间的共享资源(也是临界资源)。**因此,我们要讨论线程池中各角色之间的关系,只有讨论清楚关系了才能写出正确的并且健壮的代码。

  • 主线程和主线程之间 ------ 主线程只有一个,不讨论。
  • 主线程和从线程之间 ------ 互斥和同步:主线程在放数据的时候,从线程不能来获取数据,否则获取到的数据可能不完整,他们之间需要互斥的访问临界资源。而主线程生产数据之后,需要通知从线程来拿,任务队列中没有数据的时候,从线程需要进行排队等待,因此,他们之间具有同步关系。
  • 从线程和从线程之间 ------ 互斥和同步:一个线程在拿数据,另一个线程就不能拿,否则可能拿到一样的数据,因此,他们之间需要互斥。为了避免某些进程竞争锁的能力太强导致的线程饥饿问题,当任务队列为空的时候,多个线程需要在条件变量下排队等待,因此,他们之间需要保持同步。

线程池的实现代码

我们设计的代码需要一个任务类型、一个线程池类型、一个主程序即可

任务类型

我们的任务类型只是简单的做一下加法即可,有想法的读者可以设计自己的任务类型。

#include <iostream>
#include <string>

class Task
{
public:
    Task() {}
    Task(int num1, int num2) : _num1(num1), _num2(num2), _result(0)
    {
    }
    void Excute()
    {
        _result = _num1 + _num2;
    }
    void ResultToString() // 打印计算结果
    {
        std::cout << "Excute Task: " << std::to_string(_num1) + "+" + std::to_string(_num2) + "=" + std::to_string(_result) << std::endl;
    }
    std::string DebugToString()  // 打印生产的任务
    {
        return std::to_string(_num1) + "+" + std::to_string(_num2) + "=?";
    }
    ~Task()
    {}
private:
    int _num1;
    int _num2;
    int _result;
};

线程池类型

#include <iostream>
#include <queue>
#include <pthread.h>
#include <unistd.h>
#include "task.hpp"

#define THREAD_NUM 5

template<class T>
class ThreadPool
{
private:
    int _thread_num;             // 线程个数
    std::queue<T*> _task_queue;  // 任务队列
    pthread_mutex_t _lock;       // 互斥量,用于保护临界资源 ------ 任务队列
    pthread_cond_t _cond;        // 条件变量,维护线程池中的线程按照
private:
    void LockQueue() 
    {
        pthread_mutex_lock(&_lock);
    }

    void UnLockQueue() 
    {
        pthread_mutex_unlock(&_lock);
    }

    void WakeUpOne() 
    {
        pthread_cond_signal(&_cond);
    }

    void ThreadWait()
    {
        pthread_cond_wait(&_cond, &_lock);
    }

    bool IsEmptyQueue() 
    {
        return _task_queue.empty();
    }

    static void *handler(void *arg) 
    {
        ThreadPool* tp_ptr = (ThreadPool*)arg;
        while(true) 
        {
            // 申请锁
            tp_ptr->LockQueue();
            // 判断任务队列是否为空  如果为空就让线程去等待
            while(tp_ptr->IsEmptyQueue())
            {
                tp_ptr->ThreadWait();
            }
            // 代码走到这里说明任务队列不为空

            Task *task;             // 输出型参数,用于获取任务 
            tp_ptr->GetTask(&task); // 获取任务
            tp_ptr->UnLockQueue();  // 释放锁
            task->Excute();         // 执行任务
            task->ResultToString(); // 打印执行结果
            delete task;            // 任务是malloc的,使用完需要delete
        }
        return NULL;
    }

public:
    ThreadPool(int num = THREAD_NUM)
        :_thread_num(num)
    {
        // 构造时初始化互斥量和条件变量
        pthread_mutex_init(&_lock, NULL);
        pthread_cond_init(&_cond, NULL);
    }

    ~ThreadPool() 
    {
        // 析构时销毁互斥量和条件变量
        pthread_mutex_destroy(&_lock);
        pthread_cond_destroy(&_cond);
    }

    bool InitThreadPool() {
        pthread_t tid;
        // 创建一批线程
        for (int i = 0; i < _thread_num; i++) 
        {
            int ret = pthread_create(&tid, NULL, handler, this);
            if (ret != 0) 
            {
                std::cout<<"create pool thread error\n";
                return false;
            }
        }

        return true;
    }

    void PushTask(Task *task) 
    {
        // 加锁
        LockQueue();
        // 向任务队列中放数据
        _task_queue.push(task);
        // 通知线程池中正在等待的线程来获取任务
        WakeUpOne();
        // 解锁
        UnLockQueue();
    }

    void GetTask(Task **task) 
    {
        *task = _task_queue.front();
        _task_queue.pop();
    }
};

主程序

#include "ThreadPool.hpp"

int main()
{
    // 定义线程池
    ThreadPool<Task> thread_pool;
    // 初始化线程池
    if( thread_pool.InitThreadPool())
    {
        std::cout << "Create threadpool success" << std::endl; 
    }

    srand(time(NULL));
    while(true) {
        // 主线程创建任务
        int num1 = rand()%1000;
        int num2 = rand()%1000;
        Task* task = new Task(num1, num2);
        // 打印创建的任务
        std::cout << "Create Task: " << task->DebugToString() << std::endl;
        // 向任务队列中放入任务
        thread_pool.PushTask(task);
        // 每个1秒创建一个线程
        sleep(1);
    }

    return 0;
}

运行结果:

  • 可以看到主线程创建的任务被线程池中的线程获取并执行了。
相关推荐
ling-454 分钟前
ifconfig 不显示 Linux 虚拟机常规网卡的 IP 地址
服务器·网络·php
黎明晓月16 分钟前
‌CentOS 7.9 安装 Docker 步骤
linux·docker·centos
菜鸟xy..25 分钟前
winhex软件简单讲解,虚拟磁盘分区介绍
linux·运维·服务器
网硕互联的小客服28 分钟前
如何排查服务器内存泄漏问题
linux·运维·服务器·安全·ssh
驰驰的老爸32 分钟前
elk单机版安装
运维·jenkins
Evoxt 益沃斯38 分钟前
How to enable Qemu Guest Agent for Virtual Machines
linux·运维·服务器·qemu
钟离墨笺1 小时前
【Linux】【网络】UDP打洞-->不同子网下的客户端和服务器通信(未成功版)
linux·服务器·网络
llkk星期五1 小时前
ubuntu 22.04附加驱动安装NVIDIA显卡驱动重启后无WiFi蓝牙等问题
linux·ubuntu
CVer儿1 小时前
ubuntu挂载固态硬盘
linux·运维·ubuntu
music&movie1 小时前
Win11安装VMware和Ubuntu并使用ssh访问部署模型
linux·ubuntu·ssh