【多线程操作】线程池模拟实现

文章详细介绍了线程池的作用,如避免频繁线程创建和销毁,以及其模拟实现,包括线程模块、线程锁模块、任务模块和线程池核心。线程模块用于线程的创建和回收,线程锁模块利用RAII实现自动加锁解锁,任务模块封装不同任务,线程池核心则负责线程管理和任务调度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一.线程池的作用

二.线程池的模拟实现

1.线程模块(Thread.hpp):

2.线程锁模块(LockGuard.hpp):

3.任务模块(Task.hpp)

4.线程池核心(ThreadPool.hpp)


一.线程池的作用

线程池是用来维护多个线程的,当我们需要大量线程执行多个不同任务的时候如果不用线程池就会面临反复创建线程执行任务后再销毁的局面,这意味着需要手动控制大量进程。使用线程池可以预先创建好线程数量避免线程创建过多占用电脑资源,同时利用rall思想可对线程进行自动控制,同样的思想也可以实现对线程的自动加锁和解锁,采用仿函数可以让线程池接受任意类型的任务。

二.线程池的模拟实现

我们可以对多个模块进行封装从而实现低内居高耦合。线程池必然包含线程的创建和销毁,可以单独提出出来创建一个线程模块。(下文中的logmessage是一个打印工程日志的函数,这里不做详细介绍)

1.线程模块(Thread.hpp):

可以把线程的创建和回收用函数封装,实现线程的初始化和回收,同时给传进来的任务通过ThreadData绑定上线程的名称

#include <string.h>
#include <iostream>
#include <pthread.h>
#include "LogMessage.hpp"

typedef void *(*fun_t)(void *);  //本来想使用functaion函数,但由于pthread_create不支持所以只能写成c的形式。

class ThreadData  //线程数据包含线程名和要执行程序的void*的形参
{
public:
    std::string _name;
    void* _arg; 
};

class Thread
{
public:

    Thread(std::string name,fun_t func,void* arg)
    :_func(func)
    {
      _tdata._name = name;
      _tdata._arg = arg;
    }

    std::string name()
    {
        return _tdata._name;
    }

    void start()
    {
      pthread_create(&_pid,nullptr,_func,(void *)&_tdata);  //创建线程的同时执行要执行的任务
      logMessage(NORMAL, "线程启动成功");
    }

    void join()
    {
      pthread_join(_pid,nullptr);
    }
    ~Thread()
    {
    }
    private:
    ThreadData _tdata;
    pthread_t _pid;
    fun_t _func;
};


2.线程锁模块(LockGuard.hpp):

由于在使用多线程的时候势必或遇到线程安全的问题,我们一定需要使用到锁,可以利用rall的思想实现线程创建回收的时候自动创建锁回收锁,我们可以手动传入锁,并把创建锁和释放锁放入LockGuard的构造函数和析构函数,这样就可以在使用的时候通过花括号控制锁的生命周期从而实现自动加锁解锁。

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

class Mutex
{
public:
    Mutex(pthread_mutex_t *pmtx)
        : _pmtx(pmtx)
    {
    }

    ~Mutex()
    {
    }

    void Lock()
    {
        pthread_mutex_lock(_pmtx);
    }

    void Unlock()
    {
        pthread_mutex_unlock(_pmtx);
    }

private:
    pthread_mutex_t *_pmtx;
};

//rall方法
class LockGuard
{
public:
    LockGuard(pthread_mutex_t *pmtx)
    :_mtx(pmtx)
    {
        _mtx.Lock();
    }
    ~LockGuard()
    {
        //std::cout<<"unlock";
        _mtx.Unlock();
    }
private:
    Mutex _mtx;
};

3.任务模块(Task.hpp)

针对不同的任务我们提供一个类把它包装起来并提供一个仿函数给ThreadPool调用,这样 以后更换任务的时候就不需要更改线程池里的代码了,只需要更改task里的函数类型和仿函数调用方式就可以更换不同的任务了,这里以一个加法计算器来举例子。

#pragma once

#include <iostream>
#include <string>
#include <functional>
#include "LogMessage.hpp"

typedef std::function<int(int,int)> func_t; //更换函数的时候只需要更换这里的函数类型

class Task
{
public:
    Task()
    {}
    Task(int x, int y, func_t func)
    :_x(x)
    ,_y(y)
    , _func(func)
    {}

    void operator()(const std::string &name)
    {
        logMessage(FATAL, "%s处理完成: %d+%d=%d | %s | %d",name.c_str(),_x,_y,_func(_x,_y),__FILE__, __LINE__);  //更换这里的日志信息和操作即可
    }
public:
    int _x;
    int _y;
    func_t _func;
};




4.线程池核心(ThreadPool.hpp)

首先我们需要在构造函数内初始化条件变量和锁,并创建指定数量的线程,并完成所有的线程准备工作,并通过vector管理起来,当我们通过run拉起所有线程的时候,各个线程会执行routine函数,不同的线程执行不同的任务,不过当任务队列为空的时候各个线程是不会执行routine函数的,只有当向任务队列添加完任务后,并唤醒消费线程,线程才会执行各自的任务,并将任务任务队列中移除。唯一的注意点就是routine必须为static因为这样才不会传入this指针,不然不符合线程创建系统函数的格式要求。这样就确保线程在有资源的情况下跑起来了

#pragma once            //防止宏定义多次定义
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <unistd.h>
#include "thread.hpp"
#include "LockGuard.hpp"
#include "LogMessage.hpp"

const int defult_thread_num = 3; // 默认线程池容量

template <class T>
class ThreadPool
{
public:
    pthread_mutex_t *getMutex()
    {
        return &_lock;
    }

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

    void waitcond()
    {
        pthread_cond_wait(&_cond, &_lock);  //等待失败时会自动释放锁
    }

    T getTask()
    {
        T t = _task_queue.front();
        _task_queue.pop();
        return t;
    }

    ThreadPool(int thread_num = defult_thread_num)
        : _num(thread_num)
    { // 初始化锁和条件变量
        pthread_mutex_init(&_lock, nullptr);
        pthread_cond_init(&_cond, nullptr);

        for (int i = 0; i <= _num; i++)
        {
            char nameBuffer[64];
            snprintf(nameBuffer, sizeof nameBuffer, "Thread-%d", i);
            _threads.push_back(new Thread(nameBuffer, routine, this));
            // 传this指针是因为rountine函数为static函数无法访问类内成员,我们需要手动传入来访问内部成员
        }
        logMessage(NORMAL,  "线程池创建成功");

    }
    //消费过程
    static void *routine(void *arg) // 为满足线程的启动形式必须将this指针剔除
    {
        ThreadData *td = (ThreadData *)arg;              // 取到线程内部的数据包(void*数据的外层)
        ThreadPool<T> *tp = (ThreadPool<T> *)(td->_arg); // 取到数据包内指向自己的指针,通过这种方式调用内部成员变量

        while (true)
        {
            T task;
            {
                LockGuard lg(tp->getMutex());  
                logMessage(NORMAL,"申请锁成功");
                while(tp->isEmpty())
                {
                    tp->waitcond();
                }
                task = tp->getTask(); //将任务从公共空间拿到私有空间并将公共空间对应的任务去掉
            }
            task(td->_name);//仿函数执行拿出来的任务
        }
        logMessage(NORMAL,"消费成功");
    }

    void run()
    {
        for (auto &iter : _threads)  //拉起每一个线程
        {
            iter->start();
            logMessage(NORMAL, "%s %s", iter->name().c_str(), "启动成功");
        }
    }

    void pushTask(const T& task)   //添加任务时需要保证原子性
    {
        LockGuard lockguard(&_lock);
        _task_queue.push(task);         //确保有任务后在拉起消费者模型
        pthread_cond_signal(&_cond);   //唤醒消费者模型

    }
    ~ThreadPool()
    {
        for (auto &iter : _threads)
        {
            iter->join(); // 回收线程并删除
            delete iter;
        }
        pthread_mutex_destroy(&_lock);
        pthread_cond_destroy(&_cond);
    }

private:
    std::vector<Thread *> _threads;
    std::queue<T> _task_queue;
    pthread_mutex_t _lock;
    pthread_cond_t _cond;

    int _num; // 线程容量
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值