Linux系统:线程池


前言

  • 线程池就像一个“工人队伍”,提前雇好几个工人(线程),让他们随时待命。每个任务来时,不用临时招人(创建新线程),直接让闲着的工人去干活复用线程。干完后,工人不走,继续等下一个任务。

线程池的应用场景

  • 线程池最适合那些需要“同时干多件事,但任务短小精悍”的地方。简单说,它像一个“待命工人队”,帮你高效处理并发任务。常见场景有这些(基于实际开发经验):

  • Web服务器处理请求:比如网站收到很多用户访问(如淘宝浏览商品),每个请求是短任务,用线程池分配“工人”快速响应,避免服务器卡顿。

  • 文件上传/下载:云盘(如百度网盘)用户上传照片时,多个文件并行处理,但不让线程乱创建太多。

  • 高并发业务如秒杀/抢票:网购秒杀(双11)或12306买票,瞬间海量请求,用线程池控制线程数,防止系统崩溃。

  • IO密集型任务:数据库查询或网络调用(等IO时间长),线程池让CPU多干活,提高效率。

  • 定时/异步任务:后台定时发送邮件或处理日志,用线程池调度,避免阻塞主程序。

总之,只要有“并发但不长耗时”的任务,线程池都能帮大忙!适合服务器、APP后台等。


下面我们就来通过C/C++代码和Linux的发行版本unbuntu20.04来编写一个简单的线程池,需要用到两个步骤,


封装线程

我们需要先封装一个个体的线程包含线程的基本信息,和函数。然后再用一个线程池的类组合起来,形成我们想要的线程池任务队列
在这里插入图片描述
pthread基本实现:

using func_t = std::function<void()>;
int number=1; // bug
class Thread
{
public:
private:
    pthread_t _tid;  // 线程TID
    string _name;    // 线程名字
    bool _isdetach; // 是否分离
    bool _isrunning; // 是否运行
    void *res;       // 线程返回值
    func_t _func; // 线程处理任务的回调函数
};

在这里的_func是一个智能包装器(std::function),它内部“指向”或“存储”了线程运行时要调用的函数(回调函数)


初始化线程

在线程池中,初始化线程需要线程池将一个任务也就是函数传给线程发_func让该线程来调用这个函数,我们在线程类的构造函数内初始化,其它的值我们直接在类的内部初始化。

...
public:
Thread(func_t func):
    _tid(0),
    _isdetach(false),
    _isrunning(false),
    res(nullptr),
    _func(func)
{
    _name = "thread-" + std::to_string(number++);
}
...

开始执行

编写一个bool Start()函数,执行成功返回true,失败返回false

bool Start() {
    if (_isrunning) return false;
    int n=pthread_create(&_tid, nullptr, Routine, this);
    if (n !=0) {
        return false;
    }
    else {
        return true;
    }
}

这个Routine封装了我们后续分离线程的函数,Routine通过this指针来调用我们线程需要执行的任务

但是我们线程跑起来之后需要设置线程状态是否已经跑起来了

void EnableDetach() {
    _isdetach = true;
}
void EnableRunning() {
    _isrunning = true;
}

Routine函数实现

static void *Routine(void *args) // 属于类内的成员函数,默认包含this指针!
    {
    Thread *self=static_cast<Thread *>(args);
    self->EnableRunning();
    if (!self->_isdetach) self->Detach();
    pthread_setname_np(self->_tid, self->_name.c_str());
    self->_func(); // 回调处理
    return nullptr;
}

这里pthread_setname_np 用于给线程分配一个可读名字,方便调试、监控和日志追踪,是线程池或多线程程序中常用的辅助工具,将self->_name.c_str()信息写进self->_tid对应的线程之中,当我们打印线程信息的时候会显示线程的名称

self->Detach();用于后续我们将线程分离开来,以便管理线程

这些函数都是会封装在我们的thread的类里面,为什么这里的Routine需要一个static给它设置成一个静态函数呢,

  • pthread_create 原型:
int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine)(void *),void *arg);

这里用于接收函数的参数是void *(*start_routine)(void *)start_routine 必须是 函数指针,也就是:必须是 普通函数或静态函数,不能是非静态成员函数。

  • C++ 类的成员函数问题

普通(非静态)成员函数的签名:void* Thread::Routine(void* args);实际上会隐式传入 this 指针,签名变成:void* Routine(Thread* this, void* args);多了一个 this 参数,和 pthread_create 要求的 void* (*)(void*) 不匹配直接传 &Thread::Routinepthread_create 会报错,设置成静态成员函数就没有this指针就符合 pthread_create 要求


线程分离

我们需要实现一个线程分离,让线程池中的线程都分离开,不需要进程等待线程pthread_join,这样会更方便

void Detach() {
    if (_isdetach) return;
    if (_isrunning) pthread_detach(_tid);
    EnableDetach();
}

我们这里的Detach()可以用来将线程标记为需要分离的线程,再后面开始运行线程之后会直接将线程分离

在使用线程分离时,我们需要将线程标记为分离状态才能在运行时被分离开来

thread th(function);
th.Detach();
th.Start();

以这种先标记,再执行的方式将线程分离。


线程中止

如果我们不在运行线程,我们可以自定义中止

bool Stop() {
    if (_isrunning) {
        int n = pthread_cancel(_tid);
        if (n != 0) {
            return false;
        } else {
            _isrunning = false;
            return true;
        }
    }
    return false;
}

这里我们需要先判断线程是否在运行,然后直接中止线程,没什么特别的这里不做过多详述,只是需要用到pthread_cancel


线程等待

如果我们不需要线程分离,那么我们需要用线程等待来回收线程

void Join() {
    if (_isdetach) {
        return;
    }
    int n = pthread_join(_tid, & res);
    if (n != 0) {
        cout<< "Join线程失败"<<endl;
    } else {
        cout<< "Join线程成功";<<endl;
    }
}

这里也没什么特别的,不做过多叙述,只是需要用到pthread_join


thread完整代码

#ifndef _THREAD_H_
#define _THREAD_H_
#include <iostream>
#include <string>
#include <pthread.h>
#include <cstdio>
#include <cstring>
#include <functional>
using namespace std;
namespace ThreadModlue {
    using namespace LogModule;
    static uint32_t number = 1; // bug

    class Thread {
        using func_t = std:: function < void() > ; // 暂时这样写,完全够了
        private:
            void EnableDetach() {
                _isdetach = true;
            }
        void EnableRunning() {
            _isrunning = true;
        }
        static void * Routine(void * args) // 属于类内的成员函数,默认包含this指针!
        {
            Thread * self = static_cast < Thread * > (args);
            self -> EnableRunning();
            if (self -> _isdetach)
                self -> Detach();
            pthread_setname_np(self -> _tid, self -> _name.c_str());
            self -> _func(); // 回调处理
            return nullptr;
        }
        // bug
        public:
            Thread(func_t func): _tid(0),
            _isdetach(false),
            _isrunning(false),
            res(nullptr),
            _func(func) {
                _name = "thread-" + std::to_string(number++);
            }
        void Detach() {
            if (_isdetach)
                return;
            if (_isrunning)
                pthread_detach(_tid);
            EnableDetach();
        }
        std::string Name() {
            return _name;
        }

        bool Start() {
            if (_isrunning)
                return false;
            int n = pthread_create( & _tid, nullptr, Routine, this);
            if (n != 0) {
                return false;
            } else {
                return true;
            }
        }
        bool Stop() {
            if (_isrunning) {
                int n = pthread_cancel(_tid);
                if (n != 0) {
                    return false;
                } else {
                    _isrunning = false;
                    return true;
                }
            }
            return false;
        }
        void Join() {
                if (_isdetach) {
                    return;
                }
                int n = pthread_join(_tid, & res);
                if (n != 0) {
                    cout<< "Join线程失败"<<endl;
                } else {
                    cout << "Join线程成功"<<endl;
                }
            }
            ~Thread() {}

        private:
            pthread_t _tid;
        std::string _name;
        bool _isdetach;
        bool _isrunning;
        void * res;
        func_t _func;
    };
}

#endif

封装线程池

我们封装线程池需要有几个我们需要控制的变量:线程个数,线程存储,任务存储

static const int gnum = 5;
template <typename T>
class ThreadPool
{
public:
    ThreadPool(int num = gnum) : _num(num)
    {
    }
    ~ThreadPool()
    {
    }
private:
    std::vector<Thread> _threads; //存储线程的对象的容器
    int _num; // 线程池中,线程的个数
    std::queue<T> _taskq; //存储任务的容器
};

这里我们设置了两个容器分别存储线程和任务,因为任务需要排队被执行所以使用queue


初始化线程池

将每个线程都赋予一个任务

ThreadPool(int num = gnum): _num(num) {
    for (int i = 0; i < num; i++) {
        _threads.emplace_back(this->HandlerTask());
    }
}

在线程池的类中我们会封装一个函数HandlerTask()用于执行线程的函数,这个后面会讲解,这里的HandlerTask(),是用于线程任务的执行。


任务处理

pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
void HandlerTask() {
    while (true) {
        T t;
        {
            pthread_mutex_lock(mutex);
            while (_taskq.empty()) {
                pthread_cond_wait(&cond,&mutex);
            }
            // 一定有任务
            t = _taskq.front(); // 从q中获取任务,任务已经是线程私有的了!!!
            _taskq.pop();
            pthread_mutex_unlock(mutex);
        }
        t(); // 处理任务,需要在临界区内部处理吗?1 0
    }
}

先用线程调用HandlerTask(),会进入一个死循环,不断处理任务T,知道没有任务处理,停止线程继续等待任务。

但是如何让HandlerTask()开始执行呢我们直接调用每个threadStart()函数

void Start() {
    for (auto & thread: _threads) {
        thread.Start();
    }
}

添加任务

void PushTask(const T& task)
{
    pthread_mutex_lock(&mutex);
    _taskq.push(task);
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);
}

线程池中的线程只是执行HandlerTask(),负责执行线程池中queue<T> _taskq的任务,如果我们不往_taskq添加任务,就无输出。


完整代码

#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include "pthread.hpp"
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static const int gnum = 5;
template <typename T>
class ThreadPool
{
public:
    ThreadPool(int num = gnum) : _num(num)
    {
        for (int i = 0; i < num; i++)
        {
            _threads.emplace_back([this]()
                                  { this->HandlerTask(); });
        }
    }
    void Start()
    {
        for (auto &thread : _threads)
        {
            thread.Start();
        }
    }
    void PushTask(const T &task)
    {
        pthread_mutex_lock(&mutex);
        _taskq.push(task);
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
    }
    void HandlerTask()
    {
        char name[128];
        pthread_getname_np(pthread_self(), name, sizeof(name));
        while (true)
        {
            T t;
            {
                pthread_mutex_lock(&mutex);
                while (_taskq.empty())
                {
                    pthread_cond_wait(&cond, &mutex);
                }
                // 一定有任务
                t = _taskq.front(); // 从q中获取任务,任务已经是线程私有的了!!!
                _taskq.pop();
                pthread_mutex_unlock(&mutex);
            }
            t(); // 处理任务,需要在临界区内部处理吗?1 0
        }
    }
    ~ThreadPool()
    {
    }

private:
    std::vector<Thread> _threads;
    int _num; // 线程池中,线程的个数
    std::queue<T> _taskq;
};

演示线程池

using namespace std;
#include <iostream>
#include <unistd.h>
#include "pthreadpool.hpp"
int num = 0;
void fun()
{
    cout << "我是一个任务" << num << endl;
    num++;
}
int main()
{
    using func = function<void()>;
    func t1 = fun;
    ThreadPool<func> tp1;
    while (1)
    {
        tp1.PushTask(fun);
        tp1.PushTask(fun);
        tp1.PushTask(fun);
        tp1.PushTask(fun);
        tp1.PushTask(fun);
        tp1.Start();
        sleep(1);
    }
}

演示结果

root@hcss-ecs-f59a:/gch/code/HaoHao/learn3/day5# ./exe
我是一个任务0
我是一个任务1
我是一个任务2
我是一个任务3
我是一个任务4
......

此博客中代码健壮性还是有问题,但是个人认为这些琐碎的问题讲出来也没意思,所以就压缩了点内容,请各位见谅

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值