Linux 之 【多线程】(线程池、单例模式)

目录

1.线程池

概念

优点

工作流程

代码实现

总结

2.单例模式

概念

特点

两种实现方式

饿汉模式

懒汉方式

线程安全的懒汉方式(双重判空)

C++11后的最简实现(局部静态成员)

要点总结


1.线程池

概念

线程池 是一种池化技术 ,通过预先创建一组线程,重复利用线程执行多个任务,避免频繁创建/销毁线程的开销
本质以空间换时间------用内存预分配换取CPU调度的效率

优点

优势 原理 收益
降低开销 复用线程,避免重复创建/销毁 短任务场景性能提升10~100倍
提高响应速度 线程已就绪,任务到达即执行 无需等待线程创建,延迟降低
控制并发度 线程数量上限固定 防止资源耗尽,系统更稳定
任务管理 任务队列缓冲 平滑突发流量,削峰填谷
统一管理 监控、调优、扩展集中化 运维友好,可观测性强

工作流程

复制代码
1. 初始化阶段
   └── 预创建 corePoolSize 个工作线程,全部阻塞在任务队列上

2. 任务提交阶段
   └── 提交者(生产者)将任务封装成 Runnable/Task
       └── 任务队列.push(task)

3. 任务执行阶段
   └── 工作线程(消费者):
       ├── while(1) {
       ├──   task = 任务队列.pop()  // 队列空则阻塞
       ├──   task.run()
       └── }

4. 扩容阶段(动态线程池)
   └── 队列满 && 线程数 < maxPoolSize → 创建新线程

5. 缩容阶段
   └── 线程空闲超过 keepAliveTime → 销毁线程

6. 销毁阶段
   └── shutdown():不再接受新任务,执行完已有任务后退出
   └── shutdownNow():立即停止所有任务,返回未执行任务列表

代码实现

  • 线程入口函数

在类内定义线程入口函数时需要将其定义为静态成员函数,不然就会有隐藏的this指针导致参数不匹配

在start函数中传递 this 指针 作为入口函数的参数,这样线程入口函数就可以调用类内成员了

  • RAII锁

    class LockGuard {
    explicit LockGuard(pthread_mutex_t& mutex) {
    pthread_mutex_lock(&mutex);
    }
    ~LockGuard() {
    pthread_mutex_unlock(&mutex);
    }
    };

即使在锁内return或抛异常,也会自动解锁,杜绝了"忘记解锁"导致的死锁,代码简洁,不需要手动写unlock

  • 锁外执行任务

    {
    LockGuard guard(pool->mutex_);
    task_ptr = std::move(pool->tasks_.front());
    pool->tasks_.pop();
    } // ← 锁在这里释放!

    // 锁外执行耗时任务
    if (task_ptr) {
    (*task_ptr)();
    }

锁持有时间从"任务执行时间"降到"队列操作时间",支持长任务不阻塞其他线程取任务。任务抛异常不会影响线程池状态

  • 智能指针管理任务生命周期

    std::queue<std::shared_ptr<T>> tasks_;
    auto task_ptr = std::make_shared<T>(std::move(task));

自动释放内存,无需手动delete,线程异常退出时,未执行的任务自动释放,还可以存储派生类任务
此外,双关机既可以保证保证数据一致性,又可以用于紧急停止场景

移动语义优化大对象拷贝的性能

try、catch 保证异常安全的任务执行

复制代码
#pragma once

#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <memory>
#include <atomic>
#include <functional>
#include <pthread.h>
#include <unistd.h>

/**
 * @brief 线程池 - 工业级实现
 * 
 * 核心特性:
 * - 优雅关机:原子标志 + 条件变量广播唤醒
 * - RAII资源管理:LockGuard自动加解锁
 * - 移动语义:任务支持右值传递,零拷贝优化
 * - 任务生命周期:智能指针管理,自动释放
 * - 异常安全:RAII保证异常时资源不泄露
 * 
 * 线程安全:是(多生产者/多消费者)
 */
template<class T>
class ThreadPool {
public:
    /**
     * @brief 构造函数
     * @param num 线程数量,默认5
     */
    explicit ThreadPool(int num = 5) 
        : shutdown_(false)
        , thread_count_(num) {
        
        if (num <= 0) {
            throw std::invalid_argument("Thread count must be positive");
        }
        
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&cond_, nullptr);
    }
    
    /**
     * @brief 禁止拷贝/移动 - 包含pthread句柄
     */
    ThreadPool(const ThreadPool&) = delete;
    ThreadPool& operator=(const ThreadPool&) = delete;
    ThreadPool(ThreadPool&&) = delete;
    ThreadPool& operator=(ThreadPool&&) = delete;
    
    /**
     * @brief 析构函数 - 自动优雅关机
     */
    ~ThreadPool() {
        shutdown();  // 1. 唤醒所有线程并等待退出
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);
    }
    
    /**
     * @brief 启动线程池
     */
    void start() {
        LockGuard guard(mutex_);
        if (!threads_.empty()) return;  // 防止重复启动
        
        for (int i = 0; i < thread_count_; ++i) {
            pthread_t tid;
            pthread_create(&tid, nullptr, worker, this);
            threads_.emplace_back(tid);
        }
    }
    
    /**
     * @brief 优雅关闭线程池
     * 
     * 1. 设置关机标志(原子操作,保证内存可见性)
     * 2. 广播唤醒所有等待线程
     * 3. 等待所有线程执行完当前任务后退出
     * 4. 清空任务队列(由任务智能指针自动释放)
     */
    void shutdown() {
        shutdown_.store(true);
        
        {
            LockGuard guard(mutex_);
            pthread_cond_broadcast(&cond_);  // 唤醒所有等待线程
        }
        
        // 等待所有线程退出
        for (pthread_t tid : threads_) {
            pthread_join(tid, nullptr);
        }
        threads_.clear();
    }
    
    /**
     * @brief 立即关闭线程池
     * 
     * 1. 设置关机标志
     * 2. 广播唤醒所有线程
     * 3. 清空任务队列(未执行的任务将被销毁)
     */
    void shutdownNow() {
        shutdown_.store(true);
        
        {
            LockGuard guard(mutex_);
            // 清空任务队列,智能指针自动释放任务资源
            while (!tasks_.empty()) {
                tasks_.pop();
            }
            pthread_cond_broadcast(&cond_);
        }
        
        for (pthread_t tid : threads_) {
            pthread_join(tid, nullptr);
        }
        threads_.clear();
    }
    
    /**
     * @brief 提交任务(左值版本)
     * @param task 任务对象
     */
    void push(const T& task) {
        if (shutdown_.load()) return;
        
        // 使用智能指针管理任务生命周期
        auto task_ptr = std::make_shared<T>(task);
        
        LockGuard guard(mutex_);
        tasks_.push(std::move(task_ptr));
        pthread_cond_signal(&cond_);  // 唤醒一个工作线程
    }
    
    /**
     * @brief 提交任务(右值版本,移动语义优化)
     * @param task 任务对象
     */
    void push(T&& task) {
        if (shutdown_.load()) return;
        
        // 移动构造任务,零拷贝优化
        auto task_ptr = std::make_shared<T>(std::move(task));
        
        LockGuard guard(mutex_);
        tasks_.push(std::move(task_ptr));
        pthread_cond_signal(&cond_);
    }
    
    /**
     * @brief 获取当前任务队列大小(不精确,仅供参考)
     */
    size_t size() const {
        LockGuard guard(const_cast<pthread_mutex_t&>(mutex_));
        return tasks_.size();
    }
    
    /**
     * @brief 线程池是否已关闭
     */
    bool isShutdown() const {
        return shutdown_.load();
    }
    
private:
    /**
     * @brief RAII锁守卫
     * 
     * 优点:异常安全,自动解锁,防止遗漏
     * 原理:构造加锁,析构解锁
     */
    class LockGuard {
    public:
        explicit LockGuard(pthread_mutex_t& mutex) : mutex_(mutex) {
            pthread_mutex_lock(&mutex_);
        }
        ~LockGuard() {
            pthread_mutex_unlock(&mutex_);
        }
    private:
        pthread_mutex_t& mutex_;
    };
    
    /**
     * @brief 工作线程入口函数
     * @param arg 线程池对象指针
     * @return nullptr
     * 
     * 核心逻辑:
     * 1. 检查关机标志,决定是否退出
     * 2. 等待任务队列非空(条件变量)
     * 3. 取出任务智能指针
     * 4. 在锁外执行任务(减少锁持有时间)
     */
    static void* worker(void* arg) {
        ThreadPool* pool = static_cast<ThreadPool*>(arg);
        
        while (!pool->shutdown_.load()) {
            std::shared_ptr<T> task_ptr;
            
            {
                LockGuard guard(pool->mutex_);
                
                // while循环防止伪唤醒
                while (pool->tasks_.empty() && !pool->shutdown_.load()) {
                    pthread_cond_wait(&pool->cond_, &pool->mutex_);
                }
                
                // 关机检查:如果已关机且队列空,线程退出
                if (pool->shutdown_.load() && pool->tasks_.empty()) {
                    break;
                }
                
                // 取出任务(移动语义,避免拷贝)
                task_ptr = std::move(pool->tasks_.front());
                pool->tasks_.pop();
            }  // 解锁,锁持有时间极短
            
            //锁外执行任务!
            // 1. 减少锁竞争
            // 2. 支持长任务不阻塞其他线程
            // 3. 异常安全(任务抛异常不影响线程池状态)
            if (task_ptr) {
                try {
                    (*task_ptr)();  // 执行任务
                } catch (const std::exception& e) {
                    std::cerr << "Task execution failed: " << e.what() << std::endl;
                } catch (...) {
                    std::cerr << "Task execution failed with unknown error" << std::endl;
                }
            }
        }
        
        return nullptr;
    }
    
private:
    // 同步原语
    pthread_mutex_t mutex_;      // 互斥锁:保护任务队列
    pthread_cond_t cond_;        // 条件变量:任务队列非空等待
    
    // 线程管理
    std::vector<pthread_t> threads_;  // 线程ID列表
    int thread_count_;                // 线程数量
    
    // 任务管理
    std::queue<std::shared_ptr<T>> tasks_;  // 任务队列(智能指针自动管理生命周期)
    std::atomic<bool> shutdown_;            // 优雅关机标志(原子操作,内存可见性)
};

总结

线程池 = 生产者消费者模型 + 池化技术
生产者提交任务,消费者执行任务,队列缓冲任务,池化复用线程------以空间换时间,以控制换稳定

2.单例模式

概念

单例模式 是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点

特点

唯一性:某个类只应该有一个对象(实例)

全局访问:提供统一的全局访问接口

典型应用场景:服务器加载大量数据(上百G)到内存时,用单例类管理这些数据

两种实现方式

饿汉模式

复制代码
template <typename T>
class Singleton {
    static T data;
public:
    static T* GetInstance() {
        return &data;
    }
};

特点:程序加载时就创建实例

类比:吃完饭立刻洗碗,下一顿直接可用

优点:实现简单,线程安全

缺点:无论是否使用都会创建,可能造成资源浪费

懒汉方式

复制代码
// 基础版本(线程不安全)
template <typename T>
class Singleton {
    static T* inst;
public:
    static T* GetInstance() {
        if (inst == NULL) {
            inst = new T();  // 线程不安全点
        }
        return inst;
    }
};

特点:首次使用时才创建实例

类比:吃完饭先放着,下顿用时再洗

核心思想:延迟加载,优化启动速度

线程安全的懒汉方式(双重判空)

复制代码
template <typename T>
class Singleton {
    volatile static T* inst;  // volatile防止编译器优化
    static std::mutex lock;
    
public:
    static T* GetInstance() {
        if (inst == NULL) {           // 第一次判空:避免不必要的锁竞争
            lock.lock();               // 加锁保证线程安全
            if (inst == NULL) {        // 第二次判空:防止多次创建
                inst = new T();
            }
            lock.unlock();
        }
        return inst;
    }
};

双重判空:外层判空减少锁竞争,内层判空保证唯一创建

互斥锁:保证多线程环境下只调用一次new

volatile关键字:防止编译器过度优化

优化类型 问题描述 volatile的作用
寄存器缓存 变量值缓存在寄存器,忽略内存修改 强制每次从内存读取
指令重排 对象构造和赋值重排导致未完全构造的对象被使用 禁止相关指令重排
删除冗余读 优化掉必要的判空操作 保持所有读写操作
常量折叠 把多次读取优化为常量 保证每次读取真实值

C++11后的最简实现(局部静态成员)

复制代码
class Singleton {
private:
    // 构造函数私有
    Singleton() {}
    // 拷贝构造和赋值私有
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    
public:
    static Singleton& GetInstance() {
        static Singleton instance;  // 局部静态对象
        return instance;
    }
};

C++11保证局部静态对象初始化的线程安全性

代码简洁,只需初始化一次

不需要显式加锁

要点总结

构造函数私有化 :防止外部创建对象
拷贝构造和赋值私有 :防止拷贝产生新对象
静态成员/静态方法 :提供全局访问点
延迟加载 :懒汉模式的核心优势,优化启动速度
线程安全 :多线程环境下保证单一实例
双重判空 :提高性能的关键技术
volatile:防止指令重排序和优化问题

相关推荐
Ha_To2 小时前
2026.2.3 Dockfile创建镜像
linux·运维·服务器
筵陌2 小时前
Linux网络传输层协议TCP
linux·网络·tcp/ip
HIT_Weston2 小时前
132、【Ubuntu】【Hugo】搜索功能异常(问题解决)(二)
linux·运维·ubuntu
a1117762 小时前
Live2D 虚拟主播软件(开源Python)
java·linux·运维
jy258209560002 小时前
Linux系统离线部署MySQL详细教程(带每步骤图文教程)
linux·mysql·adb
旖旎夜光2 小时前
Linux(14)(上)
linux·网络
海盗猫鸥2 小时前
Linux基础指令2
linux·c语言
小义_2 小时前
【RH134知识点问答题】第11章 管理网络安全
linux·安全·web安全·云原生
nnbulls12 小时前
Linux环境下Tomcat的安装与配置详细指南
linux·运维·tomcat