一、线程封装的核心目标
在开始编码前,我们先明确线程封装要解决的核心问题:
- 简化接口:将原生 pthread 的创建、启动、等待、分离、终止等操作封装为类的成员函数,降低使用成本;
- 资源管理:通过类的构造 / 析构函数自动管理线程相关资源,避免内存泄漏;
- 状态管理:维护线程的运行状态(是否运行)、分离状态(是否分离),避免非法操作;
- 扩展性:支持自定义线程执行逻辑,兼容 C++ 的函数对象(std::function)。
二、完整线程封装代码解析
先贴出完整的线程封装代码(包含头文件和测试主函数),再逐模块拆解:
2.1 头文件(thread.hpp)
cpp
#pragma once
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <cstdio>
#include <functional>
// 静态计数器:为每个线程自动生成唯一编号
static uint32_t number = 1;
class Thread
{
private:
// 定义线程执行函数的类型:无返回值、无参数的函数对象
using func_t = std::function<void()>;
// 私有辅助函数:设置线程分离状态
void Enabledetach()
{
std::cout << "线程[" << _name << "]被分离了" << std::endl;
_isdetach = true;
}
// 私有辅助函数:设置线程运行状态
void Enablerunning()
{
_isrunning = true;
}
public:
// 构造函数:初始化线程执行逻辑和基础属性
Thread(func_t func)
: _isdetach(false), // 默认不分离
res(nullptr), // 线程退出返回值默认空
_isrunning(false), // 默认未运行
_tid(0), // 线程ID默认0
_func(func) // 绑定自定义执行逻辑
{
// 自动生成线程名称:thread-1、thread-2...
_name = "thread-" + std::to_string(number++);
}
// 线程分离接口:对外提供的分离操作
void detach()
{
if (_isdetach) return; // 避免重复分离
pthread_detach(_tid); // 调用pthread库分离接口
Enabledetach(); // 更新分离状态
}
// 线程入口函数(必须静态!):pthread_create要求的回调函数格式
static void *Routine(void *args)
{
// 将void*转换为Thread对象指针,获取当前线程实例
Thread *self = static_cast<Thread *>(args);
// 标记线程为运行状态
self->Enablerunning();
// 若设置了分离,执行分离操作(防御性编程)
if (self->_isdetach)
self->detach();
// 执行用户自定义的线程逻辑
self->_func();
return nullptr; // 线程退出返回值
}
// 启动线程:创建并运行线程
bool Start()
{
if (_isrunning) return false; // 避免重复启动
// 调用pthread_create创建线程
int n = pthread_create(&_tid, nullptr, Routine, this);
if (n != 0)
{
// 创建失败:打印错误信息(strerror获取错误描述)
std::cerr << "线程[" << _name << "]创建失败:" << strerror(n) << std::endl;
return false;
}
else
{
std::cout << "线程[" << _name << "]创建成功,TID:" << _tid << std::endl;
return true;
}
}
// 停止线程:强制终止运行中的线程
bool stop()
{
if (!_isrunning) return false; // 未运行则无需停止
// 调用pthread_cancel终止线程
int n = pthread_cancel(_tid);
if (n != 0)
{
std::cerr << "线程[" << _name << "]终止失败:" << strerror(n) << std::endl;
return false;
}
else
{
_isrunning = false; // 更新运行状态
std::cout << "线程[" << _name << "]终止成功" << std::endl;
return true;
}
}
// 等待线程:阻塞等待线程退出并回收资源
void Join()
{
if (_isdetach)
{
std::cout << "线程[" << _name << "]已分离,无法Join" << std::endl;
return;
}
// 调用pthread_join等待线程退出
int n = pthread_join(_tid, &res);
if (n != 0)
{
std::cerr << "线程[" << _name << "]Join失败:" << strerror(n) << std::endl;
}
else
{
std::cout << "线程[" << _name << "]Join成功,回收资源" << std::endl;
}
}
// 析构函数:空实现(可拓展资源清理逻辑)
~Thread() {}
private:
pthread_t _tid; // 线程ID(pthread库标识)
std::string _name; // 线程名称(自定义,便于调试)
bool _isdetach; // 分离状态标记:true=已分离,false=未分离
bool _isrunning; // 运行状态标记:true=运行中,false=已停止
void *res; // 线程退出返回值(pthread_join的输出参数)
func_t _func; // 线程执行的自定义逻辑
};
2.2 测试主函数(main.cpp)
cpp
#include "thread.hpp"
#include <unistd.h>
#include <iostream>
int main()
{
// 创建线程对象:绑定lambda表达式作为线程执行逻辑
Thread t([]()
{
while(true)
{
// 循环打印,模拟线程持续运行
std::cout << "线程[" << pthread_self() << "]正在运行..." << std::endl;
sleep(1);
} });
// 设置线程为分离状态(可选)
t.detach();//可以先不进行分离,观看join现象
// 启动线程
t.Start();
// 主线程等待5秒,让子线程运行
sleep(5);
// 终止子线程
t.stop();
// 主线程再等待5秒,观察线程状态
sleep(5);
// 尝试Join(因已分离,会提示失败)
t.Join();
return 0;
}
2.3 核心代码解析
(1)静态计数器与线程命名
cpp
static uint32_t number = 1;
// 构造函数中生成名称
_name = "thread-" + std::to_string(number++);
- 静态变量number生命周期贯穿程序全程,每次创建 Thread 对象时自增,确保每个线程有唯一名称;
- 线程名称便于调试时区分不同线程,比单纯的 pthread_t(数值型 ID)更直观。
(2)线程入口函数的关键设计:static Routine(重点)
cpp
static void *Routine(void *args)
{
Thread *self = static_cast<Thread *>(args);
// ... 执行逻辑
}
- 为什么必须静态? :pthread_create 要求的回调函数必须是void*()(void )类型,而类的非静态成员函数隐含this指针(实际参数为void* (Thread::)(void , this)),无法直接匹配;
也就是void *Routine(void *args) // 属于类内的成员函数,默认包含this指针!
为什么给线程入口函数加 static 还不够,还需要通过回调(_func)来执行用户逻辑
一、先明确:static 只解决「入口函数的类型匹配」问题
首先回顾 pthread 库的核心约束:
pthread_create 要求的线程入口函数必须是 全局 / 静态函数,函数签名固定为:
cpp
void* (*start_routine)(void*)
而类的非静态成员函数,编译器会自动给它加一个隐藏的 this 指针参数,实际签名是:
cpp
void* Thread::Routine(Thread* this, void* args)
这个签名和 pthread_create 要求的完全不匹配,直接用会编译报错。
给 Routine 加 static 的核心作用:
- 静态成员函数不属于任何对象实例,没有隐藏的 this 指针,签名变成 void* Routine(void*),刚好匹配 pthread_create 的要求;
- 静态成员函数可以访问类的静态成员,但无法直接访问非静态成员(比如 _func、_name)------ 所以我们才通过
pthread_create 的第四个参数把 this 指针传进去,在 Routine 内部强转后访问对象的非静态成员。
但 static 只解决了「线程能启动」的问题,并没有解决「线程要执行什么逻辑」的问题 ------ 这就是「回调」要解决的核心需求。
二、为什么必须用回调(_func)?核心是「通用性」
- 反例:如果不用回调,直接把逻辑写在 Routine 里会怎样?
cpp
// 错误示范:逻辑硬编码,完全无通用性
static void *Routine(void *args)
{
Thread *self = static_cast<Thread *>(args);
self->Enablerunning();
// 硬编码逻辑:只能打印,无法复用
while(true) {
std::cout << "固定逻辑,无法修改" << std::endl;
sleep(1);
}
return nullptr;
}
这种写法的问题:
Thread 类被「写死」了 ,只能执行 Routine 里的固定代码 ;
如果想让线程执行不同逻辑(比如计算、文件读写、网络请求),必须修改 Thread 类的源码,违反「开闭原则」(对扩展开放,对修改关闭)。
- 回调(_func)的核心价值:解耦「线程管理」和「业务逻辑」
我们把 Thread 类的职责拆成两部分:
- 线程管理(通用能力):创建、启动、停止、分离、Join 等,这部分是 Thread 类的核心职责,固定不变;
- 业务逻辑(自定义能力):线程要执行的具体任务,这部分是用户自定义的,千变万化。
通过 std::function<void()> _func 这个回调函数,我们实现了:
- 解耦:Thread 类只负责「管理线程生命周期」,不关心线程具体做什么 ;用户只需要把要执行的逻辑传给 Thread 构造函数,无需修改
Thread 类源码; - 灵活:支持任意可调用对象(普通函数、lambda、绑定了参数的函数、仿函数等),比如:
cpp
// 普通函数
void task1() { std::cout << "执行任务1" << std::endl; }
Thread t1(task1);
// lambda(带捕获)
int x = 10;
Thread t2([x]() { std::cout << "x=" << x << std::endl; });
// 绑定参数的函数
void task3(int a, std::string b) { ... }
Thread t3(std::bind(task3, 100, "hello"));
- 复用:同一个 Thread 类可以创建任意多线程,每个线程执行不同逻辑,无需重复编写线程管理代码。
三、核心逻辑梳理:static + 回调 的完整执行流程
简单总结执行步骤:
- 用户把要执行的逻辑传给 Thread 构造函数,存在 _func 里 ;
2.调用 Start() 时,pthread_create 以「静态 Routine 为入口、this 为参数」创建线程;
3.线程启动后执行 Routine,先通过 this 指针找到当前 Thread 对象;
4.最后调用 self->_func(),执行用户自定义的逻辑 ------ 这一步就是「回调」的本质。
四、总结:static 和回调的分工
| 特性 | static 关键字 | 回调(_func) |
|---|---|---|
| 解决的问题 | 让入口函数匹配 pthread 要求 | 让线程能执行任意自定义逻辑 |
| 核心作用 | 消除隐藏的 this 指针 | 解耦线程管理和业务逻辑 |
| 关系 | static 是回调的「前提」 | 回调是 static 的「目的」 |
简单来说:static 让线程能正常启动,而「回调」让线程知道该做什么 ------ 前者解决「能不能跑」的问题,后者解决「跑什么」的问题,二者缺一不可。
(3)状态管理:_isdetach 与_isrunning
- _isdetach:标记线程是否为分离状态,避免重复调用pthread_detach,也避免对分离线程调用pthread_join(分离线程无法Join);
- _isrunning:标记线程是否运行中,避免重复启动线程、对未运行线程调用pthread_cancel。
(4)核心接口解析
| 成员函数 | 功能 | 核心逻辑 |
|---|---|---|
| Start() | 创建并启动线程 | 调用pthread_create,传入静态入口函数和this指针,检查返回值处理错误 |
| detach() | 设置线程为分离状态 | 调用pthread_detach,更新_isdetach状态 |
| stop() | 强制终止线程 | 调用pthread_cancel,更新_isrunning状态 |
| Join() | 等待线程退出 | 先检查分离状态,再调用pthread_join回收资源 |
三、代码编译与运行
3.1 编译命令
由于使用了 pthread 库,编译时必须链接 pthread:
bash
g++ -o $@ $^ -std=c++11 -lpthread
- -lpthread:链接 pthread 库;
- -std=c++11:支持 lambda 表达式和 std::function。
3.2 运行结果
bash
线程[thread-1]被分离了
线程[thread-1]创建成功,TID:140709260797696
线程[140709260797696]正在运行...
线程[140709260797696]正在运行...
线程[140709260797696]正在运行...
线程[140709260797696]正在运行...
线程[140709260797696]正在运行...
线程[thread-1]终止成功
线程[thread-1]已分离,无法Join
- 线程启动后每 1 秒打印一次运行信息;
- 主线程等待 5 秒后终止子线程,尝试 Join 时因线程已分离,提示失败。
四、线程封装的进阶拓展
上面的基础封装满足了核心需求,但在实际项目中还可以进一步优化和拓展:
- 析构函数优化:避免资源泄漏
- 支持带参数的线程函数
- 线程异常处理
- 线程属性设置
- 线程池封装基础