当前位置: 首页 > news >正文

线程控制--1

一、进程与线程的

1.1 引子

  • 进程是房子,线程是房子里的人

  • 进程之间是独立的、解耦的(不同房子)

  • 线程属于同一个房子,共享房子里的资源

1.2 共享 vs 独占

线程独占的数据(不是绝对独占,只是当前分配给你):

  • 线程ID(用户态标识)——重要

  • 一组寄存器(线程的上下文数据)——最重要

  • 栈——最重要

  • errno

  • 信号屏蔽字

  • 调度优先级

线程共享的数据

  • 堆区、共享区

  • 文件描述符表

  • 每种信号的处理方式(SIG_IGN、SIG_DFL或自定义handler)

  • 当前工作目录

  • 用户ID和组ID

核心理解线程的"独占"数据,本质是执行流需要的私有上下文;其他所有资源都是进程级的,被所有线程共享。

二、POSIX线程库(pthread)

2.1 什么是pthread库

  • 与线程有关的函数构成一个完整系列,绝大多数以pthread_开头

  • 使用时要引入头文件<pthread.h>

  • 链接时要加-lpthread选项

    gcc program.c -o program -lpthread

2.2 用户级线程的实质

Linux 下的线程之所以叫用户级线程,是因为我们用用户态的pthread.so来完成线程的管理工作

  • 内核只提供轻量级进程(LWP)

  • pthread库在用户态对这些 LWP 进行封装和管理

  • 我们操作的是库提供的线程句柄,不是内核直接暴露的 LWP

三、线程创建:pthread_create

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);

参数详解

参数作用说明
thread返回线程ID输出型参数,不是 LWP,而是库维护的地址(对 LWP 的封装)
attr线程属性一般设 NULL,用默认属性
start_routine线程入口函数新线程要执行的函数(回调函数)
arg传给入口函数的参数会传递给start_routine

返回值

  • 成功:返回 0

  • 失败:返回错误码(不是设置 errno)

理解线程ID

pthread_t不是 LWP 号,而是一个地址——指向pthread库内部维护的线程控制块(TCB)的指针。LWP 是内核看到的,不需要暴露给用户。

四、线程退出

4.1 三种退出方式

方式调用者特点
return线程函数正常返回,返回值是退出信息
pthread_exit()线程内部主动终止自己,可带退出信息
pthread_cancel()其他线程取消指定线程

线程异常退出时,需要考虑退出信号之类的吗,不用,因为线程异常退出,进程也就会异常 退出,那我们也要拿到错误信号啊,那就是进程的父进程的事情了,与线程无关

4.2 pthread_exit退出

void pthread_exit(void *value_ptr);
  • value_ptr:线程的返回值(退出信息)

  • 注意需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是⽤malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

  • 应指向全局变量或malloc分配的内存,或者自己写一个字面值作为返回信息

4.3 pthread_cancel

int pthread_cancel(pthread_t thread);
  • 一个线程可以取消另一个线程,thread就是你要取消的目标线程的id

  • 适合主线程取消其他线程

4.4 一个重要区别

  • exit():终止整个进程(任何线程调用都会导致所有线程退出)

  • pthread_exit():只终止当前线程

五、线程等待:pthread_join

int pthread_join(pthread_t thread, void **value_ptr);

为什么需要等待?

  • 主线程结束意味着进程结束,所有线程会被强制终止

  • 如果不等待,新线程可能没执行完就结束了

  • 类似进程的wait,不等待会造成类似(注意是类似)僵尸的问题(虽然查不到,但存在)

调⽤该函数的线程将挂起等待,直到id为thread的线程终⽌。thread线程以不同的⽅法终⽌,通过
pthread_join得到的终⽌状态是不同的,总结如下:

1. 如果thread线程通过return返回,value_ ptr所指向的单元⾥存放的是thread线程函数的返回值。 2. 如果thread线程被别的线程调⽤pthread_ cancel异常终掉,value_ ptr所指向的单元 ⾥存放的是常数PTHREAD_ CANCELED。 3. 如果thread线程是⾃⼰调⽤pthread_exit终⽌的,value_ptr所指向的单元存放的是传给 pthread_exit的参数。 4. 如果对thread线程的终⽌状态不感兴趣,可以传NULL给value_ ptr参数。

参数

  • thread:要等待的线程ID

  • value_ptr:获取线程的返回值(二级指针)

阻塞等待

  • pthread_join是阻塞的

  • 等待成功后自动解决新线程的“僵尸”问题(释放库内资源)

六、线程ID获取:pthread_self

pthread_t pthread_self(void);

线程调用获取自己的线程ID。

可以配合pthread_join(pthread_self(void))来自我取消

七、线程传参与返回值:底层原理

1 引子

// 新线程 void* thread_routine(void* args) { return (void*)10; // 返回一个整数 } // 主线程 void *ret; pthread_join(tid, &ret); printf("线程返回值: %ld\n", (long)ret); // 输出 10

2 返回值传递的底层机制

关键问题:新线程如何把自己的返回值交给主线程?主线程如何通过pthread_join获得?

答案就在pthread库内部

  1. 程序启动时,会加载映射对应的库(如pthread.so

  2. 库内部维护线程控制块(TCB)——描述线程的结构体

  3. 线程ID的本质pthread_t就是这个结构体在库中的虚拟地址

  4. 线程退出时:返回值被写入tcb->retval

  5. pthread_join

    • 传入线程ID(即 TCB 地址)

    • 从 TCB 中取出retval

    • 通过二级指针参数输出给调用者

// pthread_join 简化逻辑 int pthread_join(pthread_t thread, void **value_ptr) { struct pthread_tcb *tcb = (struct pthread_tcb*)thread; // 等待线程结束... if (value_ptr != NULL) *value_ptr = tcb->retval; // 把返回值传给用户 return 0; }

3 为什么线程ID是地址?

  • 内核不关心用户态的线程标识

  • pthread库自己管理线程对象

  • 用地址作为 ID,可以直接定位到 TCB,高效访问

八、堆空间与线程的“拥有”关系

原则上,堆区间也是共享的,注意是原则上,因为我们声明了一个全局变量,但是只是在线程内部初始化他,那么这个变量就是该线程所拥有的,因为只有该线程知道这部分堆空间起始虚拟地址

理解

  • 堆空间本身是进程共享的(所有线程都能访问)

  • 但如果你在线程内newmalloc了一块内存,并把地址存在该线程私有的栈变量

  • 其他线程不知道这个地址,就无法访问

  • 所以从效果上看,这块堆内存被这个线程“独占”了

    void* thread_routine(void* args) { int *data = new int(10); // data 是栈上的局部变量,存着堆地址 // 其他线程不知道 data 的值,就无法访问这块堆内存 return nullptr; }

    这体现了:共享是能力,独占是使用方式


    九、线程库的核心思想总结

    层面内容
    内核层轻量级进程(LWP),用task_struct模拟线程
    库层pthread.so封装 LWP,提供线程管理接口
    用户层操作pthread_t句柄,通过库函数管理线程

    线程ID是地址——指向库内 TCB,通过它:

  • pthread_join能找到线程的控制块

  • 从中取出返回值

  • 释放线程资源

这就是为什么说 Linux 线程是用户级线程——管理逻辑在用户态库中实现,内核只提供轻量级进程作为底层支撑。

http://www.jsqmd.com/news/477420/

相关文章:

  • 专才换工作的必须提交转换雇主申请--否则成为黑工
  • 2026 联网声光报警器 优秀品牌推荐榜 智联联动 安全守护
  • 【论文解读】MAML:模型无关的元学习框架
  • 可逆跳跃马尔可夫链蒙特卡罗采样(RJMCMC)算法实现
  • 香港科技大学广州|智能制造理学硕士学位项目26Fall招生宣讲会—线上专场
  • 基于MATLAB Simulink的12脉波LCC-HVDC常规高压直流输电系统仿真建模与短路...
  • FPC叠层设计:阻抗匹配为何是信号完整性第一道生命线
  • 解析汽车车架号VIN码
  • 【linux内核】pin_user_pages原理
  • SpringMVC介绍
  • PUA OpenClaw:147只龙虾亲测有效!
  • 实习面经摘录(六)
  • /Users/yan/Downloads/boris_ImplicitDiffusion_1D_AD.jl
  • Compound 5| Compound的技术架构
  • AI提示词注入
  • Gemini PT-2D
  • 库早报|OPPO:无感折痕屏背后有两项3D打印技术;威拉里三期项目开工;五轴3D打印机TOP.E R1将亮相TCT亚洲展
  • 性价比高的铝方通哪个靠谱
  • 基于电压电流双闭环和模糊PID双环的VIENNA整流器仿真研究
  • 2026年,宁夏装修公司推荐哪家?业主实测top3家正规机构,装修小白不踩坑,附避坑指南! - 宁夏壹山网络
  • 电力弹簧主动配电网规划及优化运行调度策略探讨
  • 2026养发加盟项目怎么选?新手创业实用指南 - 品牌排行榜
  • 2026年七大闷声赚钱的AI技能
  • Deep Seek总结的APSW 和 SQLite 的关系
  • 部分视图 PartialView的详细介绍与经典用法
  • 一次性说清楚restTemplate如何使用1
  • 基于Zynq UltraScale+的FLASH固化问题记录
  • VSAR 软件 XCP/CCP 在线与离线测量操作指南
  • 矿用本安型全景雷达物位扫描仪的应用
  • AI重构小家电:恒享花有限公司与海尔在AWE以标准引领服务展示先行者的“新逻辑”与“慢功夫” - 博客湾