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> tasks_;
    auto task_ptr = std::make_shared(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:防止指令重排序和优化问题

相关推荐
且从容.2 小时前
Ubuntu安装flume
linux·ubuntu·flume
承渊政道2 小时前
Linux网络学习【Reactor反应堆模式】
linux·网络·学习·ubuntu·reactor
且从容.5 小时前
ubuntu安装flink
linux·ubuntu·flink
爱和冰阔落7 小时前
【Linux网络】TCP 服务器如何并发处理多个客户端?从 fork、僵尸进程到双重 fork
linux·运维·服务器·网络
程序员沉梦听雨7 小时前
WSL2的安装与使用
linux·wsl
微小的鱼7 小时前
Linux的渲染与显示系统链路
android·java·linux
lisanmengmeng7 小时前
alertmanger 标签 rules 打上分组标签
linux·运维·服务器
Android系统攻城狮8 小时前
Linux FFmpeg深度解析之av_write_frame调用流程与实战(三十三)
linux·运维·服务器·ffmpeg·音视频进阶·ffmpeg音视频
Linux-lucky20 小时前
45-Linux学习之旅之防火墙与WEB加固
linux·前端·网络·学习·ubuntu
禁默20 小时前
启元 Q1 二次开发实践:Ubuntu 部署与仿真验证,从 PrimeConsole 到 ROS 2 Humble + RViz
linux·运维·ubuntu