文章目录
-
- Linux线程同步与互斥(四):线程池与任务管理
- 一、为什么需要线程池
-
- [1.1 传统线程使用的问题](#1.1 传统线程使用的问题)
- [1.2 线程池的思想](#1.2 线程池的思想)
- [1.3 线程池的组成](#1.3 线程池的组成)
- 二、任务类设计
-
- [2.1 任务的抽象](#2.1 任务的抽象)
- [2.2 Task基类](#2.2 Task基类)
- [2.3 具体任务示例](#2.3 具体任务示例)
- 三、线程池实现
-
- [3.1 ThreadPool类设计](#3.1 ThreadPool类设计)
- [3.2 完整实现](#3.2 完整实现)
- [3.3 关键点解析](#3.3 关键点解析)
-
- [3.3.1 为什么ThreadRoutine是静态函数](#3.3.1 为什么ThreadRoutine是静态函数)
- [3.3.2 如何通知线程退出](#3.3.2 如何通知线程退出)
- [3.3.3 任务的生命周期](#3.3.3 任务的生命周期)
- 四、线程池测试
-
- [4.1 基本测试](#4.1 基本测试)
- [4.2 压力测试](#4.2 压力测试)
- 五、单例模式优化
-
- [5.1 为什么要单例](#5.1 为什么要单例)
- [5.2 单例模式实现](#5.2 单例模式实现)
-
- [5.2.1 懒汉式(延迟初始化)](#5.2.1 懒汉式(延迟初始化))
- [5.2.2 线程安全的懒汉式](#5.2.2 线程安全的懒汉式)
- [5.2.3 饿汉式](#5.2.3 饿汉式)
- [5.3 完整的单例线程池](#5.3 完整的单例线程池)
- 六、实战案例:简单日志系统
-
- [6.1 日志系统需求](#6.1 日志系统需求)
- [6.2 日志级别](#6.2 日志级别)
- [6.3 日志任务](#6.3 日志任务)
- [6.4 日志接口](#6.4 日志接口)
- [6.5 使用示例](#6.5 使用示例)
- 七、线程池的优化方向
-
- [7.1 动态调整线程数](#7.1 动态调整线程数)
- [7.2 任务优先级](#7.2 任务优先级)
- [7.3 任务超时控制](#7.3 任务超时控制)
- [7.4 监控和统计](#7.4 监控和统计)
- 八、本篇总结
-
- [8.1 核心知识点](#8.1 核心知识点)
- [8.2 最重要的理解](#8.2 最重要的理解)
Linux线程同步与互斥(四):线程池与任务管理
💬 重磅来袭 :前面三篇把互斥锁、条件变量、生产者消费者模型都讲清楚了,这些知识怎么用到实际项目中?这就是本篇的核心------线程池(ThreadPool)。线程池是生产者消费者模型的典型应用:用户提交任务是生产者,工作线程处理任务是消费者,任务队列作为中间容器。我们会从线程池的设计思想讲起,分析为什么需要线程池,然后一步步实现一个完整的、可用的线程池。同时会用单例模式让线程池全局可用,并实现一个简单的日志系统作为实战案例。学完这篇,你就能把多线程编程真正用到项目里了。
👍 点赞、收藏与分享:本篇包含完整的线程池实现、设计模式应用、工程实践技巧,是多线程编程的必学内容!如果对你有帮助,请点赞、收藏并分享!
🚀 循序渐进:从原理到设计,从简单实现到完善优化,一步步掌握线程池。
一、为什么需要线程池
1.1 传统线程使用的问题
先看一个传统的多线程处理方式:
cpp
void process_task(Task task) {
// 处理任务...
}
// 每来一个任务,就创建一个线程
void handle_request(Task task) {
pthread_t tid;
pthread_create(&tid, nullptr,
[](void* arg) -> void* {
Task* t = (Task*)arg;
process_task(*t);
delete t;
return nullptr;
},
new Task(task));
pthread_detach(tid); // 分离线程,自动回收
}
int main() {
while (true) {
Task task = get_task();
handle_request(task); // 每个任务一个线程
}
}
这种方式有几个严重问题:
bash
问题1:创建销毁开销大
- 创建线程:分配栈空间、设置TCB、内核调度
- 销毁线程:回收资源、更新内核数据结构
- 频繁创建销毁,性能很差
问题2:资源消耗不可控
- 并发量大时,会创建大量线程
- 每个线程默认栈空间8MB
- 1000个线程 = 8GB内存
- 系统可能撑不住
问题3:线程切换开销
- 线程越多,上下文切换越频繁
- CPU时间浪费在切换上
- 真正的计算时间反而少
问题4:难以管理
- 线程数量不确定
- 无法控制并发度
- 难以监控和调试
1.2 线程池的思想
线程池的核心思想很简单:预先创建一定数量的线程,重复使用它们来执行任务。
bash
传统方式:
任务1 → 创建线程1 → 执行 → 销毁线程1
任务2 → 创建线程2 → 执行 → 销毁线程2
任务3 → 创建线程3 → 执行 → 销毁线程3
线程池方式:
启动时:创建N个工驻)
运行时:
任务1 → 任务队列 → 线程1取出执行
任务2 → 任务队列 → 线程2取出执行
任务3 → 任务队列 → 线程3取出执行
任务4 → 任务队列 → 线程1执行完再取
...
关闭时:通知线程退出,回收资源
优点:
bash
优点1:重用线程,避免反复创建销毁
- 线程创建一次,执行多个任务
- 性能提升明显
优点2:控制并发数
- 线程数固定(如CPU核心数)
- 避免资源耗尽
- 避免过度切换
优点3:管理方便
- 统一的任务提交接口
- 可以监控队列长度
- 可以动态调整(进阶)
优点4:隔离资源
- 某个任务的逻辑错误不影响其他任务
📌 核心理解:线程池是一种"池化技术",和数据库连接池、内存池的思想一样------预先分配资源,重复使用,减少分配释放开销。
1.3 线程池的组成
bash
线程池的三个核心组件:
1. 任务队列(Task Queue)
- 存放待执行的任务
- 生产者-消费者模型的"容器"
- 需要线程安全
2. 工作线程(Worker Threads)
- 固定数量的线程
- 不断从队列取任务执行
- 生产者-消费者模型的"消费者"
3. 管理接口
- 提交任务(生产者接口)
- 启动/停止线程池
- 查询状态(可选)
流程图:
bash
线程池
┌─────────────────────────────────────┐
│ │
│ 用户提交任务 │
│ │ │
│ ↓ │
│ ┌─────────────────┐ │
│ │ 任务队列 │ │
│ │ [T1][T2][T3] │ │
│ └─────────────────┘ │
│ │ │ │ │
│ ├─────┼─────┤ │
│ ↓ ↓ ↓ │
│ [线程1][线程2][线程3] │
│ │ │ │ │
│ └─────┴─────┘ │
│ 执行任务 │
└─────────────────────────────────────┘
二、任务类设计
2.1 任务的抽象
任务是线程池要执行的工作单元。怎么表示任务?有几种方式:
bash
方式1:函数指针
typedef void (*task_func_t)(void *arg);
优点:简单直接
缺点:只能传void*,不灵活
方式2:C++11 std::function
std::function<void()>
优点:灵活,可以绑定任何可调用对象
缺点:需要C++11
方式3:自定义Task类
class Task { virtual void run() = 0; };
优点:面向对象,清晰
缺点:每种任务要继承实现
这里用方式3,因为它更清晰,也方便扩展。
2.2 Task基类
Task.hpp:
cpp
#pragma once
#include <iostream>
#include <string>
class Task
{
public:
Task() {}
virtual ~Task() {}
// 纯虚函数,子类必须实现
virtual void run() = 0;
};
2.3 具体任务示例
计算任务:
cpp
class CalcTask : public Task
{
public:
CalcTask(int x, int y, char op)
: _x(x), _y(y), _op(op)
{}
void run() override
{
int result = 0;
switch (_op) {
case '+': result = _x + _y; break;
case '-': result = _x - _y; break;
case '*': result = _x * _y; break;
case '/': result = (_y == 0) ? -1 : _x / _y; break;
case '%': result = (_y == 0) ? -1 : _x % _y; break;
default:
std::cout << "未知操作" << std::endl;
return;
}
std::cout << "计算: " << _x << " " << _op << " "
<< _y << " = " << result
<< " [线程:" << pthread_self() << "]" << std::endl;
}
private:
int _x;
int _y;
char _op;
};
IO任务:
cpp
class IOTask : public Task
{
public:
IOTask(const std::string& filename)
: _filename(filename)
{}
void run() override
{
std::cout << "读取文件: " << _filename
<< " [线程:" << pthread_self() << "]" << std::endl;
sleep(1); // 模拟IO耗时
std::cout << "文件读取完成: " << _filename << std::endl;
}
private:
std::string _filename;
};
📌 设计思路:Task是接口,具体任务继承它并实现run()。线程池只需要知道Task接口,不需要知道具体任务类型。这是面向对象的多态思想。
三、线程池实现
3.1 ThreadPool类设计
线程池需要哪些成员?
bash
数据成员:
├─ 任务队列(BlockingQueue<Task*>)
├─ 工作线程数组(vector<pthread_t>)
├─ 线程数量(int)
└─ 运行标志(bool)
函数成员:
├─ 构造函数:设置线程数
├─ Start():启动线程池
├─ Stop():停止线程池
├─ PushTask():提交任务
└─ ThreadRoutine():线程执行函数(静态)
3.2 完整实现
ThreadPool.hpp:
cpp
#pragma once
#include <iostream>
#include <vector>
#include <pthread.h>
#include "BlockingQueue.hpp"
#include "Task.hpp"
const int default_thread_num = 5;
class ThreadPool
{
public:
ThreadPool(int thread_num = default_thread_num)
: _thread_num(thread_num)
, _is_running(false)
{}
// 启动线程池
void Start()
{
if (_is_running) {
std::cout << "线程池已经在运行" << std::endl;
return;
}
_is_running = true;
std::cout << "启动线程池,线程数: " << _thread_num << std::endl;
// 创建工作线程
for (int i = 0; i < _thread_num; i++) {
pthread_t tid;
int ret = pthread_create(&tid, nullptr, ThreadRoutine, this);
if (ret != 0) {
std::cerr << "创建线程失败" << std::endl;
continue;
}
_threads.push_back(tid);
}
std::cout << "线程池启动成功" << std::endl;
}
// 提交任务
void PushTask(Task *task)
{
if (!_is_running) {
std::cout << "线程池未运行,无法提交任务" << std::endl;
return;
}
_task_queue.Push(task);
}
// 停止线程池
//约定:
//Stop() 调用后,不允许再提交任务;否则行为未定义。
void Stop()
{
if (!_is_running) {
std::cout << "线程池未运行" << std::endl;
return;
}
std::cout << "停止线程池..." << std::endl;
_is_running = false;
// 往队列中放空任务,唤醒所有等待线程
for (int i = 0; i < _thread_num; i++) {
_task_queue.Push(nullptr);
}
// 等待所有线程结束
for (auto tid : _threads) {
pthread_join(tid, nullptr);
}
_threads.clear();
std::cout << "线程池已停止" << std::endl;
}
~ThreadPool()
{
if (_is_running) {
Stop();
}
}
private:
// 线程执行函数(静态成员函数)
static void *ThreadRoutine(void *arg)
{
ThreadPool *pool = (ThreadPool *)arg;
while (true) {
Task *task = nullptr;
pool->_task_queue.Pop(&task);
// nullptr 作为退出信号
if (task == nullptr) {
std::cout << "线程 " << pthread_self()
<< " 收到退出信号" << std::endl;
break;
}
// 执行任务
task->run();
delete task; // 执行完删除任务
}
std::cout << "线程 " << pthread_self() << " 退出" << std::endl;
return nullptr;
}
private:
int _thread_num; // 线程数量
std::vector<pthread_t> _threads; // 线程ID数组
BlockingQueue<Task*> _task_queue; // 任务队列
std::atomic<bool> _is_running;//为了简化讲解,这里使用 atomic<bool> 作为运行标志
}
3.3 关键点解析
3.3.1 为什么ThreadRoutine是静态函数
bash
原因:pthread_create 的线程函数原型是:
void *(*start_routine)(void *)
它要求是一个普通函数指针或静态成员函数。
普通成员函数:
- 隐含一个 this 指针参数
- 签名是 void *(ClassName::*)(void *)
- 类型不匹配
静态成员函数:
- 没有 this 指针
- 签名是 void *(*)(void *)
- 可以作为线程函数
解决方案:
- ThreadRoutine 声明为 static
- 通过参数传入 this 指针
- 在函数内部转换回 ThreadPool*
3.3.2 如何通知线程退出
bash
问题:
工作线程在 Pop() 上阻塞等待任务
如何让它们退出?
方案1:设置标志位
while (_is_running) {
Pop(&task);
}
问题:线程可能正在 Pop() 阻塞
修改 _is_running 它也不知道
方案2:往队列放N个特殊任务
Push(nullptr); // 空任务作为退出信号
线程收到 nullptr 就退出
本实现用方案2,更可靠。
3.3.3 任务的生命周期
bash
创建:
Task *task = new CalcTask(1, 2, '+');
提交:
pool.PushTask(task); // 所有权转移给线程池
执行:
task->run(); // 线程池的工作线程执行
销毁:
delete task; // 执行完立即删除
注意:
提交后不要再访问task指针
线程池负责delete
四、线程池测试
4.1 基本测试
test_threadpool.cpp:
cpp
#include <iostream>
#include <unistd.h>
#include "ThreadPool.hpp"
#include "Task.hpp"
int main()
{
srand(time(nullptr));
// 创建线程池
ThreadPool pool(3); // 3个工作线程
pool.Start();
// 提交10个任务
const char ops[] = "+-*/%";
for (int i = 0; i < 10; i++) {
int x = rand() % 100;
int y = rand() % 100;
char op = ops[rand() % 5];
Task *task = new CalcTask(x, y, op);
pool.PushTask(task);
std::cout << "提交任务: " << x << " " << op << " " << y << std::endl;
usleep(100000); // 0.1秒
}
sleep(3); // 等待任务执行完
pool.Stop();
return 0;
}
编译运行:
bash
$_threadpool.cpp -o test -std=c++11 -lpthread
$ ./test
启动线程池,线程数: 3
线程池启动成功
提交任务: 83 + 86
计算: 83 + 86 = 169 [线程:140234567]
提交任务: 77 * 15
计算: 77 * 15 = 1155 [线程:140234568]
提交任务: 93 / 35
计算: 93 / 35 = 2 [线程:140234569]
提交任务: 86 % 92
计算: 86 % 92 = 86 [线程:140234567]
...
停止线程池...
线程 140234567 收到退出信号
线程 140234567 退出
线程 140234568 收到退出信号
线程 140234568 退出
线程 140234569 收到退出信号
线程 140234569 退出
线程池已停止
观察:
bash
1. 3个线程轮流执行任务
2. 同一个线程执行多个任务
3. 没有创建新线程
4. 停止时所有线程正常退出
4.2 压力测试
cpp
int main()
{
ThreadPool pool(5);
pool.Start();
// 提交1000个任务
for (int i = 0; i < 1000; i++) {
Task *task = new CalcTask(i, i+1, '+');
pool.PushTask(task);
}
sleep(10);
pool.Stop();
return 0;
}
运行观察CPU和内存占用:
bash
$ ./test &
[1] 12345
$ top -p 12345
PID USER PR NI VIRT RES SHR S %CPU %MEM
12345 user 20 0 15000 2000 500 S 100 0.1
观察:
- VIRT 稳定(没有不断增长)
- 线程数固定为5
- CPU占用稳定
- 内存没有泄漏
📌 对比传统方式:如果每个任务创建一个线程,1000个任务就是1000个线程,内存暴涨,系统可能崩溃。线程池把线程数限制在5个,安全可控。
五、单例模式优化
5.1 为什么要单例
现在的线程池使用方式:
cpp
ThreadPool pool(5);
pool.Start();
// 各个模块都要用线程池
module1_use_pool(&pool);
module2_use_pool(&pool);
module3_use_pool(&pool);
问题:
bash
1. 到处传递 pool 指针,麻烦
2. 可能创建多个线程池实例,浪费资源
3. 难以全局管理
理想方式:
cpp
// 任何地方都能直接获取线程池
ThreadPool::GetInstance()->PushTask(task);
这就是单例模式(Singleton)的作用。
5.2 单例模式实现
单例模式保证一个类只有一个实例,并提供全局访问点。
5.2.1 懒汉式(延迟初始化)
cpp
class ThreadPool
{
public:
// 获取单例
static ThreadPool* GetInstance()
{
if (_instance == nullptr) {
_instance = new ThreadPool();
_instance->Start();
}
return _instance;
}
// 禁止拷贝和赋值
ThreadPool(const ThreadPool&) = delete;
ThreadPool& operator=(const ThreadPool&) = delete;
// ... 其他成员函数 ...
private:
// 构造函数私有
ThreadPool(int thread_num = default_thread_num)
: _thread_num(thread_num)
, _is_running(false)
{}
static ThreadPool* _instance;
// ... 其他成员变量 ...
};
// 静态成员初始化
ThreadPool* ThreadPool::_instance = nullptr;
问题:线程不安全!
bash
情况:
线程A和线程B同时调用 GetInstance()
时刻 线程A 线程B
T1 if (_instance == nullptr)
T2 if (_instance == nullptr)
T3 _instance = new ...
T4 _instance = new ...
结果:创建了两个实例!
5.2.2 线程安全的懒汉式
cpp
class ThreadPool
{
public:
static ThreadPool* GetInstance()
{
// 双重检查锁定(Double-Checked Locking)
if (_instance == nullptr) {
pthread_mutex_lock(&_mutex);
if (_instance == nullptr) {
_instance = new ThreadPool();
_instance->Start();
}
pthread_mutex_unlock(&_mutex);
}
return _instance;
}
private:
static ThreadPool* _instance;
static pthread_mutex_t _mutex;
};
ThreadPool* ThreadPool::_instance = nullptr;
pthread_mutex_t ThreadPool::_mutex = PTHREAD_MUTEX_INITIALIZER;
为什么要两次检查?
bash
第一次检查(锁外):
- 避免每次都加锁
- 已经创建后,直接返回
第二次检查(锁内):
- 防止多个线程同时通过第一次检查
- 在锁保护下再次确认
流程:
时刻 线程A 线程B
T1 if (_instance == nullptr) ✓
T2 if (_instance == nullptr) ✓
T3 lock
T4 if (_instance == nullptr) ✓
T5 _instance = new ...
T6 unlock
T7 lock
T8 if (_instance == nullptr) ✗
T9 unlock
T10 return _instance
补充:双重检查锁在 C++ 中不推荐,仅作思想展示
5.2.3 饿汉式
cpp
class ThreadPool
{
public:
static ThreadPool* GetInstance()
{
return _instance;
}
private:
// 程序启动时就创建(静态成员在main之前初始化)
static ThreadPool* _instance;
};
// 在main函数执行前就创建
ThreadPool* ThreadPool::_instance = new ThreadPool();
特点:
bash
优点:
在程序启动阶段完成初始化,通常不会有并发问题
实现简单
注意:
不属于 C++11 标准保证的"线程安全初始化"
存在静态初始化顺序问题(static initialization order fiasco)
在 C++11 之后,更推荐"函数内 static 对象"的单例写法,它由语言标准保证线程安全初始化,并且不存在静态初始化顺序问题。
cpp
class ThreadPool
{
public:
// 获取全局唯一实例(返回引用)
static ThreadPool& GetInstance()
{
// C++11 保证:该对象的初始化是线程安全的
static ThreadPool instance;
return instance;
}
// 禁止拷贝和赋值
ThreadPool(const ThreadPool&) = delete;
ThreadPool& operator=(const ThreadPool&) = delete;
// 对外接口示例
void Start()
{
// 启动线程池
}
void Stop()
{
// 停止线程池
}
private:
// 构造函数私有,防止外部创建
ThreadPool()
{
// 初始化线程池资源
}
~ThreadPool()
{
// 释放资源(程序退出时自动调用)
}
};
函数内 static 单例在第一次调用时才创建实例,具备懒汉式的按需初始化特性; 同时其初始化过程由 C++11
标准保证线程安全,不需要显式加锁, 避免了传统懒汉式的并发问题,也不存在饿汉式的静态初始化顺序风险, 是现代 C++ 中综合最优的单例实现。
5.3 完整的单例线程池
这里用的其实是C++11之前的饿汉式,工程上也没什么问题
ThreadPool.hpp:
cpp
#pragma once
#include <iostream>
#include <vector>
#include <pthread.h>
#include "BlockingQueue.hpp"
#include "Task.hpp"
const int default_thread_num = 5;
class ThreadPool
{
public:
// 获取单例
static ThreadPool* GetInstance()
{
return _instance;
}
// 禁止拷贝和赋值
ThreadPool(const ThreadPool&) = delete;
ThreadPool& operator=(const ThreadPool&) = delete;
void Start()
{
if (_is_running) return;
for (int i = 0; i < _thread_num; i++) {
pthread_t tid;
pthread_create(&tid, nullptr, ThreadRoutine, this);
_threads.push_back(tid);
}
_is_running = true;
}
void PushTask(Task *task)
{
_task_queue.Push(task);
}
void Stop()
{
if (!_is_running) return;
_is_running = false;
for (int i = 0; i < _thread_num; i++) {
_task_queue.Push(nullptr);
}
for (auto tid : _threads) {
pthread_join(tid, nullptr);
}
_threads.clear();
}
~ThreadPool()
{
if (_is_running) {
Stop();
}
}
private:
ThreadPool(int thread_num = default_thread_num)
: _thread_num(thread_num)
, _is_running(false)
{}
static void *ThreadRoutine(void *arg)
{
ThreadPool *pool = (ThreadPool *)arg;
while (true) {
Task *task = nullptr;
pool->_task_queue.Pop(&task);
if (task == nullptr) break;
task->run();
delete task;
}
return nullptr;
}
private:
static ThreadPool* _instance;
int _thread_num;
std::vector<pthread_t> _threads;
BlockingQueue<Task*> _task_queue;
bool _is_running;
};
// 饿汉式:程序启动时创建
ThreadPool* ThreadPool::_instance = new ThreadPool();
使用方式:
cpp
int main()
{
// 启动线程池
ThreadPool::GetInstance()->Start();
// 任何地方都可以直接提交任务
for (int i = 0; i < 10; i++) {
Task *task = new CalcTask(i, i+1, '+');
ThreadPool::GetInstance()->PushTask(task);
}
sleep(3);
ThreadPool::GetInstance()->Stop();
return 0;
}
📌 单例的优势:全局只有一个线程池实例,任何地方都能通过GetInstance()访问,不需要传递指针,使用方便。
六、实战案例:简单日志系统
6.1 日志系统需求
日志系统是几乎每个项目都需要的组件:
bash
需求:
1. 能记录不同级别的日志(INFO, WARNING, ERROR)
2. 能输出到不同目标(控制台、文件)
3. 线程安全
4. 性能好(不能阻塞业务线程)
设计思路:
- 业务线程调用log函数
- log函数创建日志任务
- 提交给线程池异步处理
- 日志任务负责实际写入
6.2 日志级别
cpp
enum LogLevel
{
INFO = 0,
WARNING,
ERROR,
FATAL
};
const char* level_to_string(LogLevel level)
{
switch (level) {
case INFO: return "INFO";
case WARNING: return "WARNING";
case ERROR: return "ERROR";
case FATAL: return "FATAL";
default: return "UNKNOWN";
}
}
6.3 日志任务
LogTask.hpp:
cpp
#pragma once
#include <iostream>
#include <fstream>
#include <string>
#include <ctime>
#include "Task.hpp"
enum LogLevel { INFO = 0, WARNING, ERROR, FATAL };
const char* level_to_string(LogLevel level)
{
switch (level) {
case INFO: return "INFO";
case WARNING: return "WARNING";
case ERROR: return "ERROR";
case FATAL: return "FATAL";
default: return "UNKNOWN";
}
}
class LogTask : public Task
{
public:
LogTask(LogLevel level, const std::string& message)
: _level(level), _message(message)
{
// 记录创建时间
time_t now = time(nullptr);
char buf[64];
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", localtime(&now));
_timestamp = buf;
}
void run() override
{
// 格式:[时间] [级别] 消息
std::string log = "[" + _timestamp + "] " +
"[" + std::string(level_to_string(_level)) + "] " +
_message;
// 输出到控制台
std::cout << log << std::endl;
// 同时写入文件
std::ofstream ofs("app.log", std::ios::app);
if (ofs.is_open()) {
ofs << log << std::endl;
ofs.close();
}
}
private:
LogLevel _level;
std::string _message;
std::string _timestamp;
};
6.4 日志接口
Logger.hpp:
cpp
#pragma once
#include "ThreadPool.hpp"
#include "LogTask.hpp"
class Logger
{
public:
static void Log(LogLevel level, const std::string& message)
{
Task *task = new LogTask(level, message);
ThreadPool::GetInstance()->PushTask(task);
}
static void Info(const std::string& message)
{
Log(INFO, message);
}
static void Warning(const std::string& message)
{
Log(WARNING, message);
}
static void Error(const std::string& message)
{
Log(ERROR, message);
}
static void Fatal(const std::string& message)
{
Log(FATAL, message);
}
};
// 宏定义,方便使用
#define LOG_INFO(msg) Logger::Info(msg)
#define LOG_WARNING(msg) Logger::Warning(msg)
#define LOG_ERROR(msg) Logger::Error(msg)
#define LOG_FATAL(msg) Logger::Fatal(msg)
6.5 使用示例
cpp
#include <iostream>
#include <unistd.h>
#include "ThreadPool.hpp"
#include "Logger.hpp"
void do_something(int id)
{
LOG_INFO("开始处理任务 " + std::to_string(id));
sleep(1); // 模拟工作
if (id % 5 == 0) {
LOG_WARNING("任务 " + std::to_string(id) + " 可能有风险");
}
if (id % 10 == 0) {
LOG_ERROR("任务 " + std::to_string(id) + " 出错了");
}
LOG_INFO("任务 " + std::to_string(id) + " 完成");
}
int main()
{
// 启动线程池
ThreadPool::GetInstance()->Start();
// 模拟多线程业务
for (int i = 0; i < 20; i++) {
do_something(i);
}
sleep(3); // 等待日志写完
ThreadPool::GetInstance()->Stop();
return 0;
}
运行结果:
bash
$ ./test
[2025-01-31 10:30:15] [INFO] 开始处理任务 0
[2025-01-31 10:30:16] [INFO] 任务 0 完成
[2025-01-31 10:30:16] [INFO] 开始处理任务 1
[2025-01-31 10:30:17] [INFO] 任务 1 完成
...
[2025-01-31 10:30:20] [INFO] 开始处理任务 5
[2025-01-31 10:30:21] [WARNING] 任务 5 可能有风险
[2025-01-31 10:30:21] [INFO] 任务 5 完成
...
[2025-01-31 10:30:25] [INFO] 开始处理任务 10
[2025-01-31 10:30:26] [WARNING] 任务 10 可能有风险
[2025-01-31 10:30:26] [ERROR] 任务 10 出错了
[2025-01-31 10:30:26] [INFO] 任务 10 完成
$ cat app.log
[2025-01-31 10:30:15] [INFO] 开始处理任务 0
[2025-01-31 10:30:16] [INFO] 任务 0 完成
...
优点:
bash
1. 业务线程不阻塞
- LOG_INFO只是创建任务并提交
- 立即返回,不等待写入完成
2. 线程安全
- 多个线程调用LOG_INFO
- 通过线程池和队列保证安全
3. 使用简单
- 一行宏就能记录日志
- 不需要关心底层细节
📌 实战技巧:日志系统是线程池的典型应用。把IO操作(写文件)丢给线程池异步处理,业务线程不会被阻塞,性能更好。
说明:
真实日志系统中,通常会:
- 单独使用一个日志线程
- 或在写文件时加互斥锁
七、线程池的优化方向
7.1 动态调整线程数
现在的线程池线程数是固定的。进阶版本可以根据任务数量动态调整:
bash
策略:
- 任务队列长度 > 某个阈值:增加线程
- 线程空闲时间 > 某个阈值:减少线程
- 设置最小/最大线程数
实现:
- 需要一个管理线程监控队列
- 需要支持线程的动态创建和销毁
- 需要处理并发控制
7.2 任务优先级
bash
需求:
有些任务很重要,要优先执行
实现:
- 使用优先队列代替FIFO队列
- Task类添加priority字段
- 队列按优先级排序
7.3 任务超时控制
bash
需求:
某些任务不能执行太久
实现:
- 给Task添加超时时间
- 用额外的线程监控
- 超时后取消任务(pthread_cancel)
7.4 监控和统计
bash
有用的指标:
- 当前活跃线程数
- 队列长度
- 已完成任务数
- 平均任务耗时
- 线程利用率
实现:
添加计数器和统计变量
提供查询接口
八、本篇总结
8.1 核心知识点
1. 线程池的必要性
bash
问题:
├─ 频繁创建销毁开销大
├─ 资源消耗不可控
├─ 线程切换开销大
└─ 难以管理
方案:
预先创建固定数量线程
重复使用执行任务
2. 线程池组成
bash
三个核心:
├─ 任务队列(BlockingQueue)
├─ 工作线程(Worker Threads)
└─ 管理接口(Start/Stop/PushTask)
本质:
生产者-消费者模型的应用
3. 实现要点
bash
├─ Task抽象:纯虚基类,多态
├─ 线程函数:静态成员,传this指针
├─ 退出通知:nullptr作为特殊任务
└─ 任务生命周期:new提交,执行后delete
4. 单例模式
bash
目的:全局唯一实例,方便访问
实现方式:
├─ 懒汉式:延迟创建,需要加锁
├─ 饿汉式:启动时创建,天然线程安全(推荐)
└─ 双重检查:懒汉式的优化
5. 实战应用
bash
日志系统:
业务线程 → 创建日志任务 → 线程池异步处理
优点:
├─ 业务线程不阻塞
├─ 线程安全
└─ 使用简单
8.2 最重要的理解
📌 线程池的精髓:
bash
1. 池化技术
- 预先分配资源
- 重复使用
- 减少创建销毁开销
2. 生产消费模型
- 用户提交任务:生产者
- 工作线程执行:消费者
- 任务队列:缓冲区
3. 控制并发度
- 固定线程数
- 避免资源耗尽
- 避免过度切换
4. 解耦业务逻辑
- 业务只管提交任务
- 不关心如何执行
- 易于维护和扩展
5. 实际应用广泛
- Web服务器:处理HTTP请求
- 数据库:处理查询
- 游戏服务器:处理玩家消息
- 日志系统:异步写入
💬 总结:这四篇把Linux多线程编程从基础到实战全部讲完了。从互斥锁保护临界资源,到条件变量实现线程同步,到生产者消费者模型解耦并发,再到线程池管理任务执行------一个完整的知识体系。掌握了这些,你就能在实际项目中灵活使用多线程技术,写出高性能、线程安全的并发程序。
👍 点赞、收藏与分享:如果这个系列对你有帮助,请点赞、收藏并分享!感谢阅读,祝你在多线程编程的路上越走越远!