📢博客主页:https://blog.youkuaiyun.com/2301_779549673
📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson
📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
📢本文由 JohnKi 原创,首发于 优快云🙉
📢未来很长,值得我们全力奔赴更美好的生活✨
文章目录
📢前言
笔者最近在学习Linux
,打算对线程到网络部分
的学习做一份较为全面的总结,笔者将把这段时间的博客代码都放在gittee
仓库中,有需要可以自取
本篇博客所用代码位于
🏳️🌈一、线程的本质探秘
1.1 进程 vs 线程的"办公室哲学"
假设进程是一个公司:
- 进程:独立办公楼(独立虚拟地址空间)
- 线程:共享办公的员工(共享代码段/堆/打开文件)
// 进程fork示例
pid_t pid = fork(); // 创建新办公楼
// 线程创建示例
pthread_create(&tid, NULL, func, arg); // 招聘新员工
1.2 Linux线程实现原理
通过ps -eLf
命令观察线程
UID PID PPID LWP C NLWP STIME TTY TIME CMD
root 1 0 1 0 1 Aug29 ? 00:00:01 /sbin/init
root 123 1 123 0 3 Aug29 ? 00:00:00 /usr/sbin/sshd
root 456 123 456 0 1 Aug29 ? 00:00:00 sshd: root@pts/0
LWP:轻量级进程ID(线程本质是轻量级进程)
NLWP:线程数量
1.3 线程的"人生阶段"
[*] --> NEW: 创建线程对象
NEW --> READY: Start()
READY --> RUNNING: 被调度
RUNNING --> BLOCKED: 等待I/O
BLOCKED --> READY: 资源就绪
RUNNING --> TERMINATED: 执行完成
RUNNING --> STOPPED: Stop()
🏳️🌈二、线程的控制手段
下图为整个线程以及进程的逻辑过程,我将借助此图进行分析和说明线程的4个
主要控制手段 - 创建、停止、等待、分离
控制逻辑链:
创建线程 → 分配task_struct → 绑定执行函数 → 加入调度队列
│
├─ 停止:设置终止标志 → 释放资源 → 更新状态
├─ 等待:阻塞主线程 → 监听状态变化 → 资源回收
└─ 分离:解除关联 → 自动回收资源
2.1 线程创建逻辑
- 内核视角(对应图中task_struct)
// 内核创建线程的简化逻辑
int kernel_thread(void (*fn)(void*), void* arg) {
struct task_struct *tsk = alloc_task_struct(); // 分配task_struct
tsk->mm = current->mm; // 共享父进程mm_struct(关键!)
copy_files(tsk); // 共享文件描述符表
tsk->stack = alloc_stack(); // 分配独立内核栈
setup_thread_stack(tsk, fn, arg); // 设置入口函数
add_to_runqueue(tsk); // 加入调度队列
return tsk->pid;
}
共享地址空间:tsk->mm = current->mm(区别于进程的mm = copy_mm())
独立资源:内核栈、寄存器值、调度优先级
- 用户态API(对应图中"Linux下的线程")
// 使用pthread_create创建线程
pthread_t tid;
pthread_create(&tid, NULL, thread_func, arg);
2.2 线程停止逻辑
内核处理流程:
1. 设置tsk->flags |= PF_EXITING
2. 触发取消点(系统调用处)
3. 调用清理函数栈(pthread_cleanup_push注册的)
4. 释放线程资源
- 协作式停止(推荐方式)
// 线程函数内检查终止标志
void* thread_func(void* arg) {
ThreadContext* ctx = (ThreadContext*)arg;
while(!ctx->should_stop) { // 主动检查停止标志
// 执行任务...
}
return NULL;
}
优势:安全释放资源,避免状态不一致
- 强制停止(对应图中"停止"标签)
pthread_cancel(tid); // 发送取消请求
2.3 线程等待逻辑
为什么需要线程等待?
- 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
- 创建新的线程不会复⽤刚才退出线程的地址空间
int pthread_join(pthread_t thread, void **value_ptr);
thread:线程ID
value_ptr:它指向⼀个指针,后者指向线程的返回值
内核实现
// 内核源码片段(简化)
int do_join_thread(struct task_struct *tsk) {
while (tsk->state != TASK_DEAD) {
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(WAIT_TIME); // 让出CPU
}
unlink_thread(tsk); // 从进程线程列表中移除
free_task_struct(tsk);
}
状态转移图示
stateDiagram
[*] --> RUNNING : 创建
RUNNING --> TERMINATED : 执行完成
RUNNING --> ZOMBIE : 终止但未Join
ZOMBIE --> [*] : 被Join
调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread join得到的终止状态是不同的,总结如下:
- 如果thread线程通过return返回,value_ptr所指向的单元里存放的是thread线程函数的返回值。
- 如果thread线程被别的线程调用pthread_cancel异常终掉,value_ptr所指向的单元里存放的是常数PTHREAD CANCELED。
- 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread exit的参数。
- 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ptr参数。
2.4 线程分离逻辑
- 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
- 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
pthread_detach(tid);
内核响应:
- 修改tsk->exit_signal为SIGNAL_GROUP_EXIT
- 设置tsk->ptrace = 0(解除跟踪)
- 禁止后续Join操作
分离后的资源回收
线程终止 → 内核自动回收资源 → 无需用户干预
2.5 控制手段与内存管理
- 虚拟地址空间交互(对应mm_struct)
- 创建时:共享父进程的mm_struct(CLONE_VM标志)
- 终止时:检查引用计数,若为0则释放mm_struct
- 页表操作示例
// 线程访问共享变量时的页表行为
void* shared_var = mmap(NULL, size, PROT_READ|PROT_WRITE,
MAP_SHARED, -1, 0);
// 内核处理:
// 1. 建立共享映射
// 2. 多个线程的页表项指向同一物理页
// 3. 修改时触发COW(若未用MAP_SHARED)
🏳️🌈三、手把手实现线程库
// 目标使用方式
Thread thread([](){
std::cout << "线程执行中..." << std::endl;
});
thread.Start();
thread.Stop();
thread.Join();
thread.Detach();
3.1 状态机管理
// 线程状态枚举
enum class TSTATUS
{
NEW, // 新建未启动状态
RUNNING, // 运行中状态
STOP // 已停止状态
};
3.2 成员变量管理
private:
std::string _name; // 线程名称标识
pthread_t _tid; // 线程ID(系统级)
pid_t _pid; // 所属进程ID
bool _joinable; // 是否是分离的,默认不是(可join状态)
func_t _func; // 用户回调函数
TSTATUS _status; // 当前线程状态
3.3 构造函数 && 析构函数
// 构造函数:接收用户回调函数
Thread(func_t func) : _func(func), _status(TSTATUS::NEW), _joinable(true)
{
// 生成唯一线程名称(Thread-1, Thread-2...)
_name = "Thread-" + std::to_string(number++);
_pid = getpid(); // 记录所属进程ID
}
// 析构函数
~Thread(){}
3.4 启动线程
// 静态线程入口函数(POSIX线程要求格式)
static void *Routine(void *args)
{
// 将void*参数转换回Thread对象指针
Thread *t = static_cast<Thread *>(args);
t->_status = TSTATUS::RUNNING; // 更新线程状态
t->_func(); // 执行用户回调函数
return nullptr;
}
// 启动线程方法
bool Start()
{
if (_status != TSTATUS::RUNNING) // 防止重复启动
{
// 创建POSIX线程(Routine为入口,this指针作为参数)
// pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
// thread:指向线程ID的指针
// attr:线程属性(可选)
// start_routine:线程入口函数
// arg:入口函数参数(这里是this指针)
// 返回值:0表示成功,其他表示失败
int n = ::pthread_create(&_tid, nullptr, Routine, this); // TODO
return n == 0; // 返回创建结果
}
return false;
}
3.5 停止线程
// 强制停止线程(慎用)
bool Stop()
{
if (_status == TSTATUS::RUNNING)
{
// 发送取消请求到目标线程
int n = ::pthread_cancel(_tid);
if (n == 0) _status = TSTATUS::STOP;
return n == 0;
}
return false;
}
3.6 线程等待
// 等待线程结束(阻塞调用)
bool Join()
{
if (_joinable) // 只有非分离线程可调用
{
int n = ::pthread_join(_tid, nullptr);
if (n == 0) _status = TSTATUS::STOP;
return n == 0;
}
return false;
}
3.7 分离线程
// 设置线程分离
void Detach()
{
EnableDetach(); // 修改内部状态
pthread_detach(_tid); // 系统级分离设置
}
🏳️🌈四、添加模板
// 线程模板类(支持带参数执行)
template <typename T>
class Thread
T _data; // 数据副本(存储在栈区)
🏳️🌈五、整体代码
当前为v2版,即可用模板版
5.1 Thread.hpp
#ifndef _THREAD_HPP__
#define _THREAD_HPP__
#include <iostream>
#include <string>
#include <pthread.h>
#include <functional>
#include <sys/types.h>
#include <unistd.h>
// v2
#define v2
#ifdef v2
namespace ThreadModule
{
static int number = 1; // 线程实例计数器(对应图示中的task_struct数量)
// 线程状态机(对应图示中task_struct的状态变化)
enum class TSTATUS {
NEW, // 新建状态(已创建未启动)
RUNNING, // 运行状态(对应CPU调度队列中的task_struct)
STOP // 终止状态(等待资源回收)
};
// 线程模板类(支持带参数执行)
template <typename T>
class Thread
{
using func_t = std::function<void(T)>; // 带参数的执行函数类型
private:
// POSIX线程入口函数(需满足C链接约定)
static void* Routine(void* args) {
// 将void*参数还原为Thread对象指针(对应图示中的task_struct关联)
Thread<T>* t = static_cast<Thread<T>*>(args);
t->_status = TSTATUS::RUNNING; // 更新状态为运行中
t->_func(t->_data); // 执行用户逻辑(访问共享内存区)
return nullptr; // 线程自然终止
}
// 设置分离状态(修改图示中的task_struct标志位)
void EnableDetach() { _joinable = false; }
public:
// 构造函数:初始化执行函数和数据副本(在栈区创建数据副本)
Thread(func_t func, T data) : _func(func), _data(data),
_status(TSTATUS::NEW), _joinable(true)
{
_name = "Thread-" + std::to_string(number++); // 生成唯一标识
_pid = getpid(); // 记录所属进程PID(对应图示中的mm_struct所属进程)
}
// 启动线程(创建轻量级进程)
bool Start() {
if (_status != TSTATUS::RUNNING) {
// 创建POSIX线程(对应图示中的clone系统调用)
int n = ::pthread_create(&_tid, nullptr, Routine, this);
return n == 0; // 返回内核创建结果
}
return false; // 防止重复启动
}
// 强制终止线程(可能引发资源泄漏)
bool Stop() {
if (_status == TSTATUS::RUNNING) {
// 发送取消请求(可能中断页表查询过程)
int n = ::pthread_cancel(_tid);
if (n == 0) _status = TSTATUS::STOP;
return n == 0; // 返回内核操作结果
}
return false;
}
// 等待线程结束(处理ZOMBIE状态)
bool Join() {
if (_joinable) {
// 阻塞等待并回收资源(对应图示中的task_struct回收)
int n = ::pthread_join(_tid, nullptr);
if (n == 0) _status = TSTATUS::STOP;
return n == 0;
}
return false;
}
// 设置线程分离(自动回收资源)
void Detach() {
EnableDetach(); // 修改可连接状态
pthread_detach(_tid); // 通知内核自动回收(对应图示中的SIGNAL_GROUP_EXIT)
}
// 状态查询接口
bool IsJoinable() { return _joinable; }
std::string Name() { return _name; }
~Thread() {
// 建议添加:if(_joinable) Join(); // RAII方式自动回收
}
private:
std::string _name; // 线程名称(调试用)
pthread_t _tid; // 线程ID(对应图示中的LWP)
pid_t _pid; // 进程ID(共享mm_struct的标识)
bool _joinable; // 连接状态(控制ZOMBIE状态转换)
func_t _func; // 执行函数(存储在代码区)
TSTATUS _status; // 状态机(NEW→RUNNING→STOP)
T _data; // 数据副本(存储在栈区)
};
}
// v1
#else
namespace ThreadModule
{
using func_t = std::function<void()>; // 定义无参回调函数类型
static int number = 1; // 线程编号计数器
// 线程状态枚举
enum class TSTATUS
{
NEW, // 新建未启动状态
RUNNING, // 运行中状态
STOP // 已停止状态
};
class Thread
{
private:
// 静态线程入口函数(POSIX线程要求格式)
static void *Routine(void *args)
{
// 将void*参数转换回Thread对象指针
Thread *t = static_cast<Thread *>(args);
t->_status = TSTATUS::RUNNING; // 更新线程状态
t->_func(); // 执行用户回调函数
return nullptr;
}
// 设置分离状态(内部方法)
void EnableDetach() { _joinable = false; }
public:
// 构造函数:接收用户回调函数
Thread(func_t func) : _func(func), _status(TSTATUS::NEW), _joinable(true)
{
// 生成唯一线程名称(Thread-1, Thread-2...)
_name = "Thread-" + std::to_string(number++);
_pid = getpid(); // 记录所属进程ID
}
// 启动线程方法
bool Start()
{
if (_status != TSTATUS::RUNNING) // 防止重复启动
{
// 创建POSIX线程(Routine为入口,this指针作为参数)
// pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
// thread:指向线程ID的指针
// attr:线程属性(可选)
// start_routine:线程入口函数
// arg:入口函数参数(这里是this指针)
// 返回值:0表示成功,其他表示失败
int n = ::pthread_create(&_tid, nullptr, Routine, this); // TODO
return n == 0; // 返回创建结果
}
return false;
}
// 强制停止线程(慎用)
bool Stop()
{
if (_status == TSTATUS::RUNNING)
{
// 发送取消请求到目标线程
int n = ::pthread_cancel(_tid);
if (n == 0) _status = TSTATUS::STOP;
return n == 0;
}
return false;
}
// 等待线程结束(阻塞调用)
bool Join()
{
if (_joinable) // 只有非分离线程可调用
{
int n = ::pthread_join(_tid, nullptr);
if (n == 0) _status = TSTATUS::STOP;
return n == 0;
}
return false;
}
// 设置线程分离
void Detach()
{
EnableDetach(); // 修改内部状态
pthread_detach(_tid); // 系统级分离设置
}
// 状态查询方法
bool IsJoinable() { return _joinable; }
std::string Name() { return _name; }
~Thread(){}
private:
std::string _name; // 线程名称标识
pthread_t _tid; // 线程ID(系统级)
pid_t _pid; // 所属进程ID
bool _joinable; // 是否是分离的,默认不是(可join状态)
func_t _func; // 用户回调函数
TSTATUS _status; // 当前线程状态
};
}
#endif
#endif
5.2 Thread.cc
#include <unordered_map>
#include <memory>
#include "Thread.hpp"
#define NUM 10
using namespace ThreadModule;
// using thread_ptr_t = std::shared_ptr<ThreadModule::Thread>;
using namespace ThreadModule;
class threadData
{
public:
int max;
int start;
};
#define v2
#ifdef v2
void Count(threadData td)
{
for (int i = td.start; i < td.max; i++)
{
std::cout << "i == " << i << std::endl;
sleep(1);
}
}
#else
void Count(){
std::cout << "线程执行中..." << std::endl;
}
#endif
int main()
{
threadData td;
td.max = 60;
td.start = 50;
// v2 版
ThreadModule::Thread<threadData> thread(Count, td);
// v1 版
// ThreadModule::Thread thread(Count);
thread.Start();
thread.Join();
// 先描述,在组织!
// std::unordered_map<std::string, thread_ptr_t> threads;
// // 如果我要创建多线程呢???
// for (int i = 0; i < NUM; i++)
// {
// thread_ptr_t t = std::make_shared<ThreadModule::Thread>([](){
// while(true)
// {
// std::cout << "hello world" << std::endl;
// sleep(1);
// }
// });
// threads[t->Name()] = t;
// }
// for(auto &thread:threads)
// {
// thread.second->Start();
// }
// for(auto &thread:threads)
// {
// thread.second->Join();
// }
// ThreadModule::Thread t([](){
// while(true)
// {
// std::cout << "hello world" << std::endl;
// sleep(1);
// }
// });
// t.Start();
// std::cout << t.Name() << "is running" << std::endl;
// sleep(5);
// t.Stop();
// std::cout << "Stop thread : " << t.Name()<< std::endl;
// sleep(1);
// t.Join();
// std::cout << "Join thread : " << t.Name()<< std::endl;
return 0;
}
5.3 Makefile
bin=testThread
cc=g++
src=$(wildcard *.cc)
obj=$(src:.cc=.o)
$(bin):$(obj)
$(cc) -o $@ $^ -lpthread
%.o:%.cc
$(cc) -c $< -std=c++17
.PHONY:clean
clean:
rm -f $(bin) $(obj)
.PHONY:test
test:
echo $(src)
echo $(obj)
👥总结
本篇博文对 线程thread从内核原理到C++封装 做了一个较为详细的介绍,不知道对你有没有帮助呢
觉得博主写得还不错的三连支持下吧!会继续努力的~