043 线程池与线程封装

34 阅读8分钟

线程池与线程封装

1. 线程池

1. ThreadPool.hpp

#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>
using namespace std;

// 线程信息结构体:保存每个线程的基本信息
struct ThreadInfo
{
    pthread_t tid;                                                   // 线程 ID
    string name;                                                     // 线程名称
};

static const int default_num = 5;                                    // 默认线程数量

template <class T>
class ThreadPool
{
public:
    // 加锁操作:保护共享资源不被多个线程同时访问
    void Lock()
    {
        pthread_mutex_lock(&mutex_);                                 // 获取互斥锁
    }
    
    // 解锁操作:释放互斥锁,让其他线程可以访问共享资源
    void Unlock()
    {
        pthread_mutex_unlock(&mutex_);                               // 释放互斥锁
    }
    
    // 唤醒一个等待的线程:当有新任务时唤醒空闲的线程
    void Wakeup()
    {
        pthread_cond_signal(&cond_);                                 // 唤醒一个等待的线程
    }
    
    // 线程睡眠等待:让线程进入等待状态,节省 CPU 资源
    void ThreadSleep()
    {
        pthread_cond_wait(&cond_, &mutex_);                          // 等待条件变量,会自动释放锁
    }
    
    // 判断任务队列是否为空
    bool IsQueueEmpty()
    {
        return tasks_.empty();                                       // 返回任务队列是否为空
    }
    
    // 根据线程 ID 获取线程名称:用于日志输出,知道是哪个线程在工作
    string GetThreadName(pthread_t tid)
    {
        for (const auto &ti : threads_)
        {
            if (ti.tid == tid)
                return ti.name;
        }

        return "Unknown";                                            // 找不到返回未知
    }

public:
    // 线程执行函数:每个工作线程都会执行这个函数
    static void *HandlerTask(void *args)
    {
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);      // 获取线程池对象指针
        string name = tp->GetThreadName(pthread_self());             // 获取当前线程的名字
        
        while (true)                                                 // 线程一直运行,不会退出
        {
            tp->Lock();                                              // 加锁保护任务队列
            
            // 等待任务:如果任务队列为空,线程就睡觉等待
            while (tp->IsQueueEmpty())
            {
                cout << name << " 等待任务..." << endl;
                tp->ThreadSleep();                                   // 睡觉等待新任务
            }
            
            T t = tp->Pop();                                         // 从队列取出一个任务
            tp->Unlock();                                            // 解锁,让其他线程可以访问队列

            t();                                                     // 执行任务(调用任务的 operator())
            cout << name << " 运行任务,结果是:" << t.get_result() << endl;
        }
        
        return nullptr;
    }
    
    // 启动线程池:创建所有的工作线程
    void Start()
    {
        int num = threads_.size();                                   // 获取要创建的线程数量
        cout << "启动 " << num << " 个线程..." << endl;
        
        for (int i = 0; i < num; i++)
        {
            threads_[i].name = "[线程 " + to_string(i + 1) + "]";    // 给每个线程起名字
            // 创建线程:&threads_[i].tid 保存线程 ID,HandlerTask 是线程要执行的函数,this 是传给函数的参数
            pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this);
            cout << "创建线程 " << threads_[i].name << endl;
        }
    }
    
    // 从任务队列取出任务
    T Pop()
    {
        T t = tasks_.front();                                        // 取出队列第一个任务
        tasks_.pop();                                                // 从队列中删除这个任务
        return t;                                                    // 返回任务
    }
    
    // 向任务队列添加任务:外部程序调用这个函数来提交任务
    void Push(const T &t)
    {
        Lock();                                                      // 加锁
        tasks_.push(t);                                              // 把新任务加入队列
        Wakeup();                                                    // 唤醒一个睡觉的线程来处理任务
        Unlock();                                                    // 解锁
    }
    
    // 获取线程池单例实例:保证整个程序只有一个线程池对象
    static ThreadPool<T> *GetInstance()
    {
        // 第一次检查:如果已经创建了实例,就直接返回(不需要加锁,提高性能)
        if (nullptr == tp_)                                          
        {
            pthread_mutex_lock(&lock_);                              // 加锁:防止多个线程同时创建实例
            // 第二次检查:再次确认还没有创建实例(防止在等待锁的时候其他线程已经创建了)
            if (nullptr == tp_)                                      
            {
                cout << "log:单例线程池首次创建完成!" << endl;
                tp_ = new ThreadPool<T>();                           // 创建唯一的线程池实例
            }
            pthread_mutex_unlock(&lock_);                            // 解锁
        }

        return tp_;                                                  // 返回线程池实例
    }

private:
    // 私有构造函数:防止外部直接创建对象,只能通过 GetInstance 获取
    ThreadPool(int num = default_num) : threads_(num)
    {
        pthread_mutex_init(&mutex_, nullptr);                        // 初始化互斥锁
        pthread_cond_init(&cond_, nullptr);                          // 初始化条件变量
    }
    
    // 私有析构函数:防止外部随意销毁线程池
    ~ThreadPool()
    {
        pthread_mutex_destroy(&mutex_);                              // 销毁互斥锁
        pthread_cond_destroy(&cond_);                                // 销毁条件变量
    }
    
    // 禁用拷贝构造函数:防止线程池对象被复制(因为线程不能被复制)
    // ThreadPool tp1; ThreadPool tp2 = tp1; // 这样就不允许
    ThreadPool(const ThreadPool<T> &) = delete;
    
    // 禁用赋值操作符:防止线程池对象被赋值(因为线程不能被赋值)
    // ThreadPool tp1, tp2; tp2 = tp1; // 这样就不允许
    const ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;

private:
    vector<ThreadInfo> threads_;                                     // 保存所有工作线程的信息
    queue<T> tasks_;                                                 // 任务队列:存放等待执行的任务

    pthread_mutex_t mutex_;                                          // 互斥锁:保护任务队列不被多个线程同时修改
    pthread_cond_t cond_;                                            // 条件变量:用于线程间的等待和唤醒

    // 单例指针:指向唯一的线程池实例
    // static 修饰:这个变量属于类本身,不属于任何对象,所有对象共享这一个变量
    static ThreadPool<T> *tp_;                                       
    
    // 单例保护锁:保护创建单例实例的过程,防止多个线程同时创建
    // static 修饰:这个锁属于类本身,用于保护静态成员变量 tp_
    static pthread_mutex_t lock_;                                    
};

// 下面是静态成员变量定义:必须在类外单独定义,这些变量在程序启动时就存在,不依赖于任何对象

// 开始时为空,第一次调用 GetInstance 时才会创建
template <class T>
ThreadPool<T> *ThreadPool<T>::tp_ = nullptr;                         

// 使用 PTHREAD_MUTEX_INITIALIZER 初始化
template <class T>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER;    

2. Task.hpp

#pragma once
#include <iostream>
#include <string>
using namespace std;

const string opers = "+-*/%";                                        // 支持的操作符集合

// 错误代码枚举定义
enum
{
    SUCCESS = 0,                                                     // 成功
    DIV_ERROR = 1,                                                   // 除零错误
    MOD_ERROR = 2,                                                   // 取模零错误
    UNKNOWN_ERROR = 3                                                // 未知操作符错误
};

class Task
{
public:
    // 默认构造函数
    Task()
        :_x(0), _y(0), _op('+'), _ret(0), _code(SUCCESS)
    {}
    
    // 带参数构造函数:初始化任务参数
    Task(int x, int y, char op = '+')
        :_x(x),
        _y(y),
        _op(op),
        _ret(0),
        _code(SUCCESS)
    {
        // 验证操作符是否合法
        if(op != '+' && op != '-' && op != '*' && op != '/' && op != '%')
        {
            _op = '+';                                               // 默认为加法
            _code = UNKNOWN_ERROR;                                   // 设置错误码
        }
    }

    // 执行运算任务
    void run()
    {
        // 重置结果和错误码(避免重复调用时的问题)
        _ret = 0;
        _code = SUCCESS;
        
        switch(_op)
        {
            case '+':
                _ret = _x + _y;
                break;
            case '-':
                _ret = _x - _y;
                break;
            case '*':
                _ret = _x * _y;
                break;
            case '/':
                if(_y == 0)
                {
                    _code = DIV_ERROR;                               // 除零错误
                    _ret = 0;
                }
                else
                {
                    _ret = _x / _y;
                }
                break;
            case '%':
                if(_y == 0)
                {
                    _code = MOD_ERROR;                               // 取模零错误
                    _ret = 0;
                }
                else
                {
                    _ret = _x % _y;
                }
                break;
            default:
                _code = UNKNOWN_ERROR;                               // 未知操作符错误
                _ret = 0;
                break;
        }
    }

    // 重载函数调用操作符,使 Task 对象可以像函数一样调用
    void operator()()
    {
        run();                                                       // 执行运算
    }

    // 获取任务描述字符串
    string get_task() const
    {
        return to_string(_x) + _op + to_string(_y) + "= ???";        // 格式:x op y = ???
    }

    // 获取运算结果字符串
    string get_ret() const
    {
        string ret = to_string(_x) + _op + to_string(_y) + "=" + to_string(_ret) + 
                     " [错误代码:" + to_string(_code) + "]";
        return ret;
    }

    // 获取操作符
    char get_operator() const
    {
        return _op;
    }

    // 获取第一个操作数
    int get_first_operand() const
    {
        return _x;
    }

    // 获取第二个操作数
    int get_second_operand() const
    {
        return _y;
    }

    // 获取运算结果
    int get_result() const
    {
        return _ret;
    }

    // 获取错误代码
    int get_error_code() const
    {
        return _code;
    }

    // 析构函数
    ~Task()
    {
    }

private:
    int _x, _y;                                                      // 两个操作数
    int _ret;                                                        // 运算结果
    char _op;                                                        // 操作符
    int _code;                                                       // 错误代码
};

3. Main.cc

#include "ThreadPool.hpp"
#include "Task.hpp"
#include <signal.h>

// pthread_spinlock_t slock;                                    // 全局自旋锁
volatile bool running = true;                                   // 信号处理标志,用于优雅退出

// 信号处理函数:捕获 Ctrl+C 等退出信号
void signalHandler(int signum)
{
    cout << "\n接收到信号 " << signum << ",正在退出..." << endl;
    running = false;
}

int main()
{
    // 注册信号处理函数,捕获 Ctrl+C (SIGINT) 和终止信号 (SIGTERM)
    signal(SIGINT, signalHandler);
    signal(SIGTERM, signalHandler);
    
    cout << "线程池启动中..." << endl;
    sleep(1);
    
    ThreadPool<Task>* tp = ThreadPool<Task>::GetInstance();      // 获取线程池单例实例并启动线程池
    tp->Start();  // 启动工作线程
    
    srand(time(nullptr) ^ getpid());                             // 设置随机种子
    
    while(running)                                               // 持续生成任务
    {
        // 1. 构建随机任务
        int x = rand() % 20 + 1;                                 // 生成 [1,20] 的随机数
        usleep(10);                                              // 微秒延迟,增加随机性
        int y = rand() % 10;                                     // 生成 [0,9] 的随机数
        char op = opers[rand() % opers.size()];                  // 随机选择操作符

        Task t(x, y, op);                                        // 创建任务对象
        
        // 2. 将任务提交给线程池处理
        tp->Push(t);                                             // 添加任务到线程池
        
        cout << "[主线程] 创建任务: " << t.get_task() << endl;

        sleep(1);                                                // 每 1 秒生成一个任务
    }
    
    cout << "程序正常退出" << endl;
    return 0;
}

2. C++ 语言层面上的线程封装 demo(简易)

1. MyThread.hpp

#pragma once
#include <iostream>
#include <pthread.h>
#include <string>
#include <ctime>
using namespace std;

typedef void (*callback_t)();                                        // 无参数无返回值的函数指针
static int thread_num = 0;                                           // 全局线程编号计数器

class Thread
{
public:
    static void* thread_func(void* arg)                              // 线程函数
    {
        Thread* thread = static_cast<Thread*>(arg);                  // 转换为线程指针
        thread->Enter_callback();

    }
public:
    Thread(callback_t cb)
        :tid_(0),                                                    // 初始化线程 ID 为 0
        name_(""),                                                   // 初始化线程名称为空
        start_timestamp_(0),                                         // 初始化启动时间戳为 0
        isrunning_(false),                                           // 初始化运行状态为 false
        cb_(cb)                                                      // 保存回调函数
    {

    }

    ~Thread()                                                        // 析构函数
    {

    }

    void run()                                                       // 启动线程
    {
        name_ = "thread-" + to_string(thread_num++);                 // 设置线程名称
        start_timestamp_ = time(nullptr);                            // 记录启动时间戳
        isrunning_ = true;                                           // 设置运行状态为 true
        pthread_create(&tid_, nullptr, thread_func, this);           // 创建线程
    }

    void Enter_callback()
    {
        cb_();                                                       // 执行回调函数
    }

    bool is_runing()                                                 // 判断线程是否运行
    {
        return isrunning_;                                           // 返回运行状态
    }

    uint64_t start_timestamp()                                       // 获取启动时间戳
    {
        return start_timestamp_;                                     // 返回启动时间戳
    }

    string name()                                                    // 获取线程名称
    {
        return name_;                                                // 返回线程名称
    }

    void join()                                                      // 等待线程结束
    {
        pthread_join(tid_, nullptr);                                 // 等待线程结束
        isrunning_ = false;                                          // 设置运行状态为 false
    }

private:
    pthread_t tid_;                                                  // 线程 ID
    string name_;                                                    // 线程名称
    uint64_t start_timestamp_;                                       // 启动时间戳
    bool isrunning_;                                                 // 运行状态标志

    callback_t cb_;                                                  // 回调函数指针
};

2. Main.cc

#include "MyThread.hpp"
#include <vector>
#include <unistd.h>

int threads_num = 3;

void print()
{
    int count = 0;
    while(count < threads_num)
    {
        cout << "我是一个 C++ 语言层面上封装的线程!" << "代号是:" << count++ << endl;
        sleep(1);
    }

    cout << "所有线程都结束了!" << endl;
}

int main()
{
    vector<Thread> threads;
    for (int i = 0; i < threads_num; ++i)
    {
        threads.push_back(Thread(print));
    }

    cout << "开始启动所有线程喽~" << endl;

    for(auto &x : threads)
    {
        x.run();
        cout << "线程 " << x.name() << " 启动成功!其时间戳的值是:" << x.start_timestamp() << endl;
    }

    for(auto &x : threads)
    {
        x.join();
    }

    cout << "所有线程都结束了!" << endl;

    return 0;
}

当然,这个 demo 仅实现了两个函数的封装,也没有进行加锁,会导致数据竞争,整体来说并不完美,仅为了展示如何实现底层封装。