【Linux】多线程开发封神之路:Linux 页表基础 + pthread 实战 + 底层原理拆解

【Linux】多线程开发封神之路:Linux 页表基础 + pthread 实战 + 底层原理拆解
前言:欢迎各位光临本博客,这里小编带你直接手撕**,文章并不复杂,愿诸君**耐其心性,忘却杂尘,道有所长!!!!
在这里插入图片描述

IF’Maxue个人主页
 🔥 个人专栏:
《C语言》
《C++深度学习》
《Linux》
《数据结构》
《数学建模》

⛺️生活是默默的坚持,毅力是永久的享受。不破不立!

文章目录

一、页表与页表项

1. 页表项标志位

页表项的核心作用是记录内存地址及访问控制信息,其中标志位是关键控制字段。

image.png

上图展示了页表项中常用标志位的定义,这些比特位各自承担特定功能:

  • 比如“存在位”标记该页表项是否有效(是否对应物理内存);
  • “读写位”控制对该内存页的读写权限;
  • 其他标志位还可能涉及缓存策略、特权级检查等,直接影响内存访问的安全性和效率。

2. 页表结构体

页表通过结构体组织管理,结构体的字段对应页表项的核心属性。

image.png

从结构体定义可明确两个核心点:

  • 页表项的本质是 unsigned long 类型的无符号整数,其核心功能是存储物理地址——这个地址指向内存中描述该页配置信息的区域;
  • 页全局目录(PGD)是页表的顶层结构,其指向的类型是下一级页表(或直接指向物理页),形成多级页表的层级关系。

3. 页目录空间申请

页目录作为页表的顶层结构,其空间申请遵循固定规则。

image.png

关键结论:一个页全局目录(PGD)的大小固定为4KB,这与Linux系统中默认的内存页大小一致——意味着页目录本身就占用一个完整的内存页,方便内存管理单元(MMU)快速定位。

4. 页表的本质:4KB数组

申请页表的过程,本质是申请一块4KB的内存空间,并将其当作数组使用。

image.png

页表的核心特性:

  • 可将4KB的页表空间视为一个数组,数组的每个元素都是 unsigned long 类型的页表项(PTE);
  • 每个PTE存储的是物理地址,通过多级页表的索引(如PGD→PMD→PTE),可最终定位到要访问的物理内存页。
image.png

5. 页全局目录(PGD)详解

PGD是多级页表的入口,其结构直接决定页表的索引效率。

image.png

PGD的核心作用:

  • 作为页表的“根节点”,每个PGD项指向一级页目录(或下一级页表);
  • 进程的虚拟地址空间通过PGD进行划分,不同进程拥有独立的PGD,实现地址空间隔离。

二、线程核心操作实战

1. 线程创建与参数传递

线程创建的核心是通过 pthread_create 函数,同时要注意参数传递和返回值的处理。

线程代码框架

image.png

线程退出信息获取:pthread_join

pthread_join 是主线程等待子线程结束的关键函数,核心作用是获取子线程的退出信息。

image.png

关键说明:

  • 函数原型 int pthread_join(pthread_t thread, void **retval),其中 retval 是二级指针,用于接收子线程的返回值;
  • 主线程调用 pthread_join 后会阻塞,直到指定子线程结束;
  • 线程结束的两种场景:
    1. 主线程结束(通常意味着进程结束,所有子线程会被强制终止);
    2. 子线程的入口函数执行完毕(正常终止)。

参数与返回值:支持任意类型

线程的参数传递和返回值具有极高灵活性——可支持任意数据类型,核心是通过 void* 指针实现通用化。

image.png
(1)传递函数类型

子线程的入口函数必须遵循固定原型:void* (*start_routine)(void*),即接收一个 void* 参数,返回一个 void* 值。

image.png
(2)返回结果

子线程通过 return 返回结果,主线程通过 pthread_joinretval 参数接收,接收后需进行类型强转。

image.png
(3)主函数示例

完整的线程创建、参数传递、返回值获取示例:

image.png

2. 线程终止的3种方式

线程终止需区分“线程终止”和“进程终止”,避免误操作导致整个进程退出。

核心方式:

  1. 入口函数return:子线程执行完入口函数后 return,是最安全的终止方式,会自动清理线程栈资源;
  2. 禁止使用exit()exit() 是进程终止函数,调用后会终止整个进程(包括所有子线程),线程中绝对不能用;
  3. pthread_cancel:主动取消指定线程,需注意线程的“可取消状态”(默认允许取消)。
image.png

注意:使用 pthread_join 时,默认认为子线程是“正常终止”(无异常),若子线程被 pthread_cancel 取消,retval 会接收特殊值(如 PTHREAD_CANCELED)。

3. 线程分离:自动释放资源

默认情况下,线程是“可连接状态(joinable)”,主线程必须调用 pthread_join 等待其结束,否则会导致资源泄露。若主线程无需关心子线程状态,可设置线程为“分离状态(detach)”。

核心理解:

  • 分离状态类比“分家”:主线程不再等待子线程,子线程结束后会自动释放资源(线程控制块、栈等);
  • 分离后的线程仍在进程地址空间中,可正常访问进程的所有资源(全局变量、堆内存等),仅主线程无需再“等待”。
image.png

两种分离方式

(1)主线程主动分离:pthread_detach

主线程调用 pthread_detach 函数,将指定子线程设置为分离状态。

image.png

函数原型:int pthread_detach(pthread_t thread),参数为要分离的线程ID。

关键注意:分离后的线程不能再调用 pthread_join,否则会返回错误(如 EINVAL)。

image.png
(2)子线程自我分离

子线程通过 pthread_self() 获取自身ID,然后调用 pthread_detach 实现自我分离,无需主线程干预。

image.png

示例代码逻辑:

void*thread_func(void* arg){// 自我分离pthread_detach(pthread_self());// 线程业务逻辑printf("子线程自我分离\n");returnNULL;}

分离失败的错误信息

若对已分离的线程调用 pthread_join,会返回错误代码,可通过 strerror 查看具体原因。

image.png

错误原因:Invalid argument,本质是“线程已被分离,无法进行连接操作”。

4. 多线程创建实战与问题排查

(1)基础多线程代码demo

循环创建多个线程,主线程等待所有子线程结束:

image.png

主线程需“逐个等待”子线程:

image.png

运行结果(无sleep时,线程执行顺序由调度器决定):

image.png

(2)问题:线程ID打印重复

若在子线程中加入 sleep,会出现“所有线程打印的ID都是9”的问题:

image.png
image.png

运行结果:

image.png
问题原因:
  • 传递给线程的是“数组元素的地址”(如 &i),而非元素的值;
  • 主线程循环速度极快,子线程因 sleep 未及时读取地址中的值,导致后续循环覆盖了 i 的值(最终 i 循环到9),所有子线程读取到的都是最后一个值。
解决方案:动态分配内存

为每个线程单独分配一块内存存储ID,避免地址被覆盖:

image.png
image.png

核心修改:

// 替换 int i; 为动态分配int* p = new int(i);pthread_create(&tid[i],NULL, thread_func, p);// 子线程中读取并释放内存void*thread_func(void* arg){int id =*(int*)arg;delete(int*)arg;// 释放动态内存printf("线程ID: %d\n", id);returnNULL;}

修改后,每个线程获取独立的ID值,不会出现重复。

三、线程底层原理:Linux轻量级进程

1. 进程与线程的地址空间关系

Linux中没有真正的线程,线程是通过“轻量级进程(LWP)”模拟实现的:

  • 进程是资源分配的基本单位(拥有独立地址空间、文件描述符等);
  • 线程是调度的基本单位(共享进程的地址空间,仅拥有独立的线程栈、寄存器等)。
image.png

动态库加载与地址空间映射:

  • 可执行程序加载时,会将依赖的动态库(如 libpthread.so)加载到内存,并映射到当前进程的地址空间;
  • 所有线程共享该地址空间,因此可直接调用动态库中的函数(如 pthread_create)。
image.png

2. 线程的两级管理模型

线程的管理由“用户态库(pthread库)”和“内核态LWP”共同实现:

  • 内核态:创建轻量级进程(LWP),负责线程的调度(CPU分配);
  • 用户态:pthread库维护线程控制块(TCB),管理线程的状态、参数、返回值等。
image.png

TCB的组织方式:

  • pthread库通过数组管理多个线程的TCB,每个TCB对应一个LWP;
  • TCB中存储线程的关键信息:线程ID、状态(joinable/detach)、返回值、线程栈地址等。
image.png

3. 线程栈与资源释放

(1)线程栈的必要性

每个线程必须拥有独立的栈空间(默认大小通常为2MB),用于存储局部变量、函数调用栈帧等,避免多线程间数据冲突。

(2)资源泄露的根源

线程结束后,若主线程未调用 pthread_join

  • 线程的栈资源会自动释放,但TCB(线程控制块)不会;
  • TCB存储在pthread库的动态内存中,未释放的TCB会导致内存泄露(ps 命令无法检测,需通过内存分析工具排查)。
image.png

解决方案:

  • 要么调用 pthread_join 等待线程结束,释放TCB;
  • 要么设置线程为分离状态,线程结束后pthread库自动释放TCB。

4. 线程创建的底层流程

pthread_create 函数的核心工作分为两步,涉及用户态和内核态的交互:

image.png

步骤1:用户态创建TCB

pthread库在用户态分配内存,创建线程控制块(TCB),初始化线程ID、状态(默认joinable)、参数、返回值存储地址等。

步骤2:内核态创建LWP

通过系统调用 clone() 创建轻量级进程(LWP),核心是:

  • 共享父进程的地址空间(文件描述符、内存映射等);
  • 为LWP分配独立的线程栈、寄存器上下文;
  • 将LWP与TCB关联,让调度器能通过LWP找到对应的线程信息。

四、关键概念深入解析

1. 用户线程与LWP的联动模型

Linux采用“1:1”线程模型:一个用户线程对应一个内核轻量级进程(LWP)。

image.png
image.png

通俗类比:“带饭”案例

  • 你(用户线程):负责维护“带饭需求”(线程的参数、返回值、状态等),不需要亲自去买饭;
  • 张三(LWP):负责执行“买饭”(线程的业务逻辑),是内核调度的对象;
  • 联动逻辑:你把需求告诉张三,张三执行完后把结果(饭)反馈给你——用户线程只需维护TCB信息,LWP负责实际执行,执行结果写入TCB供用户线程获取。
image.png

2. 线程ID的本质

  • 线程ID(pthread_t 类型)并非内核态的LWP ID,而是用户态TCB的起始虚拟地址
  • 作用:pthread库通过该ID定位TCB,实现线程的管理(如 pthread_joinpthread_cancel);
  • 类比:身份证(LWP ID,内核唯一) vs 学号(线程ID,进程内唯一)。

3. 线程返回值的存储逻辑

  • 子线程执行完后,返回值(void* 类型)会写入TCB中的指定字段;
  • 主线程通过 pthread_joinretval 参数,读取TCB中的返回值;
  • 若线程分离,返回值会随TCB自动释放,主线程无法获取。

4. 源码视角:pthread_create的实现

glibc-2.4 源码可看出 pthread_create 的核心逻辑:

image.png
image.png
image.png

关键流程:

  1. 初始化线程属性(如栈大小、分离状态);
  2. 分配TCB和线程栈内存;
  3. 调用 clone() 系统调用创建LWP;
  4. 关联TCB与LWP,返回线程ID。

5. 核心系统调用:clone

clone() 是创建LWP的底层系统调用,与 fork() 的区别是支持“资源共享”。

image.png

clone() 的宏定义与封装:

image.png
image.png

核心参数:

  • 共享标志(如 CLONE_VM 共享内存空间、CLONE_FILES 共享文件描述符);
  • 线程栈地址(为LWP分配的独立栈);
  • 函数入口(子线程要执行的业务逻辑)。

Read more

大数据新视界 -- Impala 性能优化:分布式环境中的优化新视野(下)(28 / 30)

大数据新视界 -- Impala 性能优化:分布式环境中的优化新视野(下)(28 / 30)

💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的博客,正是这样一个温暖美好的所在。在这里,你们不仅能够收获既富有趣味又极为实用的内容知识,还可以毫无拘束地畅所欲言,尽情分享自己独特的见解。我真诚地期待着你们的到来,愿我们能在这片小小的天地里共同成长,共同进步。💖💖💖 本博客的精华专栏: 1. 大数据新视界专栏系列:聚焦大数据,展技术应用,推动进步拓展新视野。 2. Java 大厂面试专栏系列:提供大厂面试的相关技巧和经验,助力求职。 3. Python 魅力之旅:探索数据与智能的奥秘专栏系列:走进 Python 的精彩天地,感受数据处理与智能应用的独特魅力。 4. Java 性能优化传奇之旅:铸就编程巅峰之路:如一把神奇钥匙,深度开启 JVM 等关键领域之门。丰富案例似璀璨繁星,引领你踏上编程巅峰的壮丽征程。 5. Java 虚拟机(

By Ne0inhk
Flutter for OpenHarmony 实战:Hive CE — 极速 NoSQL 本地存储

Flutter for OpenHarmony 实战:Hive CE — 极速 NoSQL 本地存储

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 在 Flutter for OpenHarmony 应用开发中,数据持久化是构建流畅体验的核心基石。无论是用户的登录状态、应用主题偏好,还是海量的离线缓存数据,都需要一套既快速又可靠的存储方案。 传统的 SQLite 虽然功能强大,但在处理简单的键值对(Key-Value)时往往显得过于沉重。Hive CE (Community Edition) 凭借其纯 Dart 编写、读写性能卓越的优势,成为了鸿蒙开发者的首选。本文将结合鸿蒙插件适配的最佳实践,带你构建一个工业级的加密存储方案。 一、Hive CE 的底层优势解析 1.1 纯 Dart 的并行优势 Hive 完全由 Dart 实现。在鸿蒙系统上,这意味着它避开了复杂的 JNI 调用开销。

By Ne0inhk
Flutter 组件 spinify 适配鸿蒙 HarmonyOS 实战:实时消息管道,构建全场景高性能 WebSocket 长连接架构

Flutter 组件 spinify 适配鸿蒙 HarmonyOS 实战:实时消息管道,构建全场景高性能 WebSocket 长连接架构

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 spinify 适配鸿蒙 HarmonyOS 实战:实时消息管道,构建全场景高性能 WebSocket 长连接架构 前言 在鸿蒙(OpenHarmony)生态迈向万物互联、涉及高频实时交互、流式数据同步或多人协同编辑的场景下,如何建立一套稳定、高效且具备自动愈合能力的长连接通道,已成为提升应用实时性体验的“关键枢轴”。在鸿蒙设备这类强调分布式协同与严苛能效管理的移动终端上,如果直接使用原生的 WebSocket 进行裸奔(Bare Metal)开发,由于由于缺乏完善的心跳机制、重连策略与频道管理,极易由于由于网络波动导致连接频繁断档,进而引发业务状态的不一致。 我们需要一种能够深度封装协议细节、支持大规模并发频道订阅且具备毫秒级重连恢复能力的实时通讯引擎。 spinify 为 Flutter 开发者提供了与 Centrifugo(高性能实时消息服务器)交互的高级客户端。它支持全双工通信、自动重连计数与消息序列确认(ACK)。在适配到鸿

By Ne0inhk