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

线程相关知识

线程是进程内的一条独立执行流,是操作系统调度 CPU 的最小单位,共享进程的地址空间与资源,有自己独立的栈、寄存器、程序计数器。


一、核心本质拆解

1.从属关系
进程是资源分配最小单位(内存、文件、句柄);
线程是CPU 调度最小单位,寄生在进程里,一个进程至少有 1 个主线程。
2.资源共享 & 私有
共享:代码段、全局变量、堆内存、文件描述符、信号处理方式
私有:线程栈、寄存器上下文、PC 程序计数器、线程局部变量 TLS
3.轻量级的根本原因
创建 / 销毁线程不用重新分配虚拟地址空间,只配私有栈和 TCB(线程控制块),开销远小于进程;
切换线程不用切换页表、刷新 TLB,切换成本极低。

二、线程的状态

线程五大核心状态汇总表

注:基于 Linux/Java 通用线程模型,适配面试核心考点,区分核心特征与流转逻辑

线程状态

核心定义

核心特征

触发场景

下一状态

NEW 新建

线程对象已创建,操作系统内核线程未初始化,未进入调度队列

未分配CPU资源,未执行任何代码,无调度资格

程序初始化线程对象,尚未调用启动方法

调用start()/启动线程 → READY 就绪

READY 就绪

线程资源已准备完成,具备运行条件,等待CPU时间片调度

不占用CPU,常驻调度队列,随时可被内核选中执行

1. 新建线程启动后;2. 线程时间片用完被剥夺CPU;3. 阻塞线程等待的事件完成

获取CPU时间片 → RUNNING 运行

RUNNING 运行

线程正在CPU上执行业务代码,是唯一占用CPU的状态

占用CPU资源,执行指令、处理逻辑,消耗时间片

操作系统调度器选中就绪线程,分配CPU时间片

1. 时间片耗尽 → READY;2. 等待锁/IO/sleep → BLOCKED;3. 代码执行完毕 → TERMINATED

BLOCKED 阻塞/等待

线程主动放弃CPU,暂停执行,等待外部事件触发

不占用、不抢占CPU,彻底释放时间片,是并发高效的关键

1. 等待锁(mutex);2. 阻塞式IO读写;3. 调用sleep/wait;4. 等待条件变量

等待事件触发/超时唤醒 → READY 就绪

TERMINATED 终止

线程代码执行完毕或异常终止,生命周期结束

彻底结束调度,等待系统回收线程栈、TCB等资源,无法再次启动

1. 线程主方法执行完成;2. 线程被主动终止;3. 运行中抛出致命异常

无(生命周期终结)

面试核心总结
  • 只有运行态占用CPU,就绪、阻塞态均不占用CPU资源

  • 线程不会从阻塞态直接进入运行态,必须先回到就绪态,等待CPU调度

  • 阻塞是主动放弃CPU,就绪是等待CPU分配,二者本质区别:是否具备立刻执行的条件

三、线程和进程区别

对比维度进程 (Process)线程 (Thread)
资源独立性拥有独立的内存空间、文件描述符等系统资源共享所属进程的内存空间和资源,仅拥有独立的栈、程序计数器等
创建 / 切换开销开销大(涉及资源分配与回收、页表切换、TLB 刷新)开销小(仅需保存少量上下文信息,无需切换地址空间)
通信方式需通过进程间通信(IPC)机制(如信号、消息队列、管道、共享内存、Socket 等)可直接通过共享内存通信(需同步机制避免冲突,如互斥锁、条件变量)
健壮性一个进程崩溃通常不影响其他进程一个线程崩溃可能导致整个进程崩溃
调度单位操作系统资源分配的最小单位操作系统 CPU 调度的最小单位
上下文切换成本高(需切换页表、刷新 TLB、保存完整进程上下文)低(仅需保存线程栈、寄存器上下文,同进程共享地址空间)
数据共享效率低(需通过 IPC 机制,涉及内核态切换与数据拷贝)高(直接读写进程共享内存,无额外拷贝开销)
适用场景- 任务间需要严格隔离(如浏览器标签页)- 充分利用多核 CPU 处理 CPU 密集型任务(如大规模计算)- 长时间运行的独立任务(如后台备份、视频转码)- 运行不稳定或第三方代码(避免崩溃影响整体)- 任务间需要频繁共享数据(如 Web 服务器处理多请求)- 轻量级并发,追求低开销(如 GUI 程序后台加载数据)- I/O 密集型任务(如网络请求、文件读写,可利用等待时间切换)- 短期、协作紧密的子任务(如数据分片处理后汇总)

四、多线程相关函数

1.创建与终止函数

(1)pthread_create 创建线程

项目内容
所需头文件#include <pthread.h>
函数原型int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
功能在调用进程中创建一个新的线程
参数thread输出参数,用于存储新创建线程的唯一标识符(线程 ID)
参数attr指定新线程的属性,NULL表示使用系统默认属性
参数start_routine线程的入口函数指针,线程创建后将执行这个函数
参数arg传递给入口函数的参数,无参数时设为NULL
返回值成功返回0,线程 ID 写入thread;失败返回非 0 错误码

(2)pthread_exit 线程退出

项目内容
所需头文件#include <pthread.h>
函数原型void pthread_exit(void *retval);
功能结束当前线程,并可返回退出状态值
参数retval传递给回收线程的退出状态值,不需要则传NULL
返回值

示例代码

#include <stdio.h> #include <stdlib.h> #include <pthread.h> // 必须包含 pthread 头文件 #include <unistd.h> // 用于 sleep() // 1. 定义线程入口函数:必须符合 void* (*)(void*) 的签名 void *thread_func(void *arg) { // 把主线程传来的参数强转为 int 类型 int thread_num = *(int *)arg; free(arg); // 释放主线程动态分配的参数内存(避免内存泄漏) printf("子线程 %d 开始运行\n", thread_num); // 模拟耗时任务 sleep(2); // 2. 线程退出,并返回状态值(这里用线程号作为示例) pthread_exit((void *)(long)thread_num); } int main() { int thread_count = 3; pthread_t threads[thread_count]; // 存储线程ID的数组 for (int i = 0; i < thread_count; i++) { // 动态分配内存传递参数(避免栈变量被覆盖的经典坑) int *arg = malloc(sizeof(int)); *arg = i; // 3. 创建线程 int ret = pthread_create( &threads[i], // 输出:线程ID NULL, // 使用默认线程属性 thread_func, // 线程入口函数 arg // 传递给入口函数的参数 ); if (ret != 0) { perror("pthread_create 失败"); exit(EXIT_FAILURE); } } printf("主线程等待所有子线程结束...\n"); // 4. 等待所有子线程结束,并回收资源 for (int i = 0; i < thread_count; i++) { void *retval; pthread_join(threads[i], &retval); // 阻塞等待子线程退出 // 强转回 int,打印子线程的退出状态 printf("子线程 %d 已退出,返回值:%ld\n", i, (long)retval); } printf("所有子线程已结束,主线程退出\n"); return 0; }

2.线程标识函数

(1)pthread_self函数

项目内容
所需头文件#include <pthread.h>
函数原型pthread_t pthread_self(void);
功能获取调用该函数的线程自身的线程 ID
参数
返回值总是成功,返回调用线程的线程 ID(类型为pthread_t

pthread_self() 是无参数、无失败情况的函数,调用它只会返回当前线程的 ID。
它和 pthread_create 中获取的线程 ID 是同一种类型 pthread_t,但不能直接和操作系统的 LWP(轻量级进程 ID)划等号。
常用于线程内部打印日志、调试,或作为线程的唯一标识使用

(2)示例代码

#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> // 线程入口函数 void *thread_func(void *arg) { int thread_num = *(int *)arg; free(arg); // ✅ 关键:用 pthread_self() 获取当前线程的ID pthread_t tid = pthread_self(); printf("子线程 %d 运行,线程ID: %lu\n", thread_num, (unsigned long)tid); sleep(1); pthread_exit((void *)(long)thread_num); } int main() { int thread_count = 2; pthread_t threads[thread_count]; printf("主线程启动,主线程ID: %lu\n", (unsigned long)pthread_self()); for (int i = 0; i < thread_count; i++) { int *arg = malloc(sizeof(int)); *arg = i; int ret = pthread_create(&threads[i], NULL, thread_func, arg); if (ret != 0) { perror("pthread_create failed"); exit(EXIT_FAILURE); } } // 等待所有子线程结束 for (int i = 0; i < thread_count; i++) { void *retval; pthread_join(threads[i], &retval); printf("子线程 %d 已退出\n", (int)(long)retval); } printf("所有子线程结束,主线程退出\n"); return 0; }

1.pthread_t 类型
它是一个不透明类型,通常是无符号长整型,所以打印时需要强转为 unsigned long 类型。
2.pthread_self() vs gettid()
pthread_self() 返回的是用户态线程库中的线程 ID,仅在进程内唯一。
gettid() 返回的是内核 LWP ID,全局唯一,但不是标准库函数,需要系统调用。
3.典型用途
线程内部调试日志(打印当前线程 ID)
线程局部存储(TLS)中作为键使用
线程间互斥 / 同步时标识身份

3.线程回收函数

(1)pthread_join 线程回收

项目内容
所需头文件#include <pthread.h>
原型int pthread_join(pthread_t thread, void **retval);
功能阻塞等待指定的结合态线程结束,并回收其资源
参数thread要回收的线程 ID
参数retval接收线程退出状态值的指针,不关心则传NULL
返回值成功返回0;失败返回非 0 错误码

(2)pthread_attr_init 初始化线程属性

项目内容
所需头文件#include <pthread.h>
原型int pthread_attr_init(pthread_attr_t *attr);
功能将一个线程属性对象初始化为默认值
参数attr需要初始化的线程属性对象指针
返回值成功返回0;失败返回非 0 错误码

(3)pthread_attr_setdetachstate 设置分离状态

项目内容
所需头文件#include <pthread.h>
原型int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
功能设置线程属性对象的分离状态
参数attr要设置的线程属性对象指针
参数detachstate线程状态:-PTHREAD_CREATE_JOINABLE:结合态(默认,需join回收)-PTHREAD_CREATE_DETACHED:分离态(系统自动回收资源)
返回值成功返回0;失败返回非 0 错误码

(4)pthread_attr_destroy 销毁线程属性

项目内容
所需头文件#include <pthread_attr_t *attr);
功能销毁一个已初始化的线程属性对象
参数attr要销毁的线程属性对象指针
返回值成功返回0;失败返回非 0 错误码

(5)结合态与分离态

状态特点适用场景
结合态 (JOINABLE)线程结束后资源不会自动释放,需其他线程调用pthread_join回收;可获取退出状态主线程需要等待子线程完成、需要子线程返回结果的场景
分离态 (DETACHED)线程结束后资源由系统自动回收,无需也无法join;无法获取退出状态主线程不关心子线程结果,只想让其后台运行的场景

(6)示例代码

#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> // 线程入口函数 void *thread_func(void *arg) { int id = *(int *)arg; free(arg); printf("线程 %d 开始运行,tid: %lu\n", id, (unsigned long)pthread_self()); sleep(1); // 模拟耗时任务 printf("线程 %d 运行结束\n", id); pthread_exit((void *)(long)id); } int main() { pthread_t tid_joinable, tid_detached; pthread_attr_t attr; // -------------------- 1. 创建结合态线程(默认) -------------------- int *arg1 = malloc(sizeof(int)); *arg1 = 1; pthread_create(&tid_joinable, NULL, thread_func, arg1); // -------------------- 2. 创建分离态线程(通过属性设置) -------------------- int *arg2 = malloc(sizeof(int)); *arg2 = 2; // 初始化线程属性 pthread_attr_init(&attr); // 设置为分离态 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 使用自定义属性创建线程 pthread_create(&tid_detached, &attr, thread_func, arg2); // 销毁属性对象(属性对象可以重复使用,这里用完直接销毁) pthread_attr_destroy(&attr); // -------------------- 3. 回收结合态线程 -------------------- void *retval; printf("主线程等待结合态线程1结束...\n"); pthread_join(tid_joinable, &retval); printf("结合态线程1已回收,退出状态:%ld\n", (long)retval); // 注意:分离态线程无法被join,也不需要手动回收 printf("主线程结束,分离态线程2将由系统自动回收资源\n"); sleep(2); // 等待分离态线程执行完毕,防止主线程提前退出 return 0; }

(7)核心注意事项


1.pthread_join 只能回收结合态线程
对分离态线程调用 pthread_join 会直接失败,返回 EINVAL 错误。
2.线程属性对象可复用
初始化后的 pthread_attr_t 可以多次用于创建线程,用完再销毁即可。
3.主线程退出会杀死所有子线程
即使是分离态线程,如果主线程提前 exit(),所有子线程都会被强制终止,所以主线程需要确保子线程执行完毕。
4.线程退出方式
pthread_exit():仅结束当前线程,不影响其他线程
return:在线程函数中 return 等价于 pthread_exit()
exit():结束整个进程,所有线程都会终止

4.线程控制函数

(1)pthread_detach 分离线程

项目内容
所需头文件#include <pthread.h>
原型int pthread_detach(pthread_t thread);
功能将一个已创建的线程标记为分离态,线程结束后系统自动回收资源
参数thread要设置为分离态的线程 ID
返回值成功返回0;失败返回非 0 错误码

与 pthread_attr_setdetachstate 的区别:
pthread_attr_setdetachstate:创建线程前,通过属性设置为分离态。
pthread_detach:创建线程后,将其转为分离态。

(2)pthread_cancel 取消线程

项目内容
所需头文件#include <pthread.h>
原型int pthread_cancel(pthread_t thread);
功能向指定线程发送取消请求,目标线程的处理方式取决于取消状态和类型
参数thread要取消的线程 ID
返回值成功返回0;失败返回非 0 错误码

(3)pthread_setcancelstate 设置取消状态

项目内容
所需头文件#include <pthread.h>
原型int pthread_setcancelstate(int state, int *oldstate);
功能设置调用线程的可取消状态
参数state新的取消状态:-PTHREAD_CANCEL_ENABLE:可被取消(默认)-PTHREAD_CANCEL_DISABLE:不可被取消
参数oldstate用于保存之前的取消状态,可用于恢复
返回值成功返回0;失败返回非 0 错误码

(4)pthread_setcanceltype 设置取消类型

项目内容
所需头文件#include <pthread.h>
原型int pthread_setcanceltype(int type, int *oldtype);
功能设置调用线程的取消响应方式
参数type新的取消类型:-PTHREAD_CANCEL_DEFERRED:延时取消(默认),线程执行到取消点时才退出-PTHREAD_CANCEL_ASYNCHRONOUS:异步取消,收到请求后立即退出
参数oldtype用于保存之前的取消类型
返回值成功返回0;失败返回非 0 错误码

(5) 关键解析

取消点(Cancel Point)
当线程处于延时取消模式时,只有在调用特定系统函数(如 sleep()、read()、write()、pthread_cond_wait() 等)时,才会响应取消请求并退出。
取消线程的风险
直接取消线程可能导致资源泄漏(如未释放的锁、未关闭的文件句柄)。
建议配合清理函数 pthread_cleanup_push/pop 使用,确保线程退出前能释放资源。

(6)完整示例代码

#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> // 线程清理函数:线程被取消时自动调用 void cleanup(void *arg) { printf("线程清理:释放资源,参数=%p\n", arg); } void *thread_func(void *arg) { // 注册清理函数(栈式注册,push和pop必须成对出现) pthread_cleanup_push(cleanup, (void*)0x1234); printf("子线程开始运行,tid: %lu\n", (unsigned long)pthread_self()); // 设置取消类型为延时取消(默认) int oldtype; pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype); // 模拟耗时任务,sleep是取消点 for (int i = 0; i < 5; i++) { printf("子线程运行中...%d\n", i); sleep(1); } printf("子线程正常结束\n"); // 弹出清理函数(若线程正常结束,清理函数不会被执行) pthread_cleanup_pop(0); return NULL; } int main() { pthread_t tid; pthread_create(&tid, NULL, thread_func, NULL); sleep(2); // 让子线程运行一段时间 printf("主线程发送取消请求\n"); pthread_cancel(tid); // 发送取消请求 // 等待线程结束并回收资源 void *retval; pthread_join(tid, &retval); if (retval == PTHREAD_CANCELED) { printf("线程已被取消\n"); } else { printf("线程正常结束,返回值=%p\n", retval); } return 0; }

(7)核心注意事项


pthread_cancel 只是发送请求,不是强制终止。线程是否响应、何时响应,取决于其取消状态和类型。
分离态线程也可以被取消,但主线程无法通过 pthread_join 获取其退出状态。
尽量避免异步取消,它会导致线程在任意位置终止,极易造成资源泄漏和状态不一致。
清理函数 pthread_cleanup_push/pop 必须成对使用,且必须写在同一函数的同一层级中,否则会导致栈

5.线程的通信方式

方式核心作用适用场景特点Linux 常用 API
共享全局 / 静态变量直接传递数据简单数据交互天然共享、效率最高;存在数据竞争,必须加锁保护直接读写全局变量
互斥锁 mutex保证临界区同一时刻只一个线程进入多线程争抢共享资源只能互斥、不能等待;简单易用pthread_mutex_lock/unlock
条件变量 cond线程等待 / 唤醒,条件不满足就阻塞生产者 - 消费者、任务队列必须配合互斥锁;精准唤醒线程pthread_cond_wait/signal/broadcast
信号量 semaphore资源计数、同步 + 互斥限流、多生产者多消费者可跨线程 / 跨进程;支持设置资源个数sem_wait/sem_post
读写锁 rwlock读共享、写独占读多写少场景(配置、缓存)并发读效率远高于互斥锁pthread_rwlock_rdlock/wrlock
线程信号 / 事件异步通知、唤醒阻塞线程异常中断、事件触发偏向通知机制,不适合传大量数据Linux:线程信号Windows:Event 事件

传数据:共享变量
防竞争:互斥锁
等条件、排队干活:条件变量
限流计数:信号量
读多写少优化:读写锁
异步通知唤醒:线程信号 / 事件

五、总结

1、pthread 线程生命周期 全景表格

阶段核心函数功能关键参数 / 返回重要考点
创建线程pthread_create创建一个新线程成功 = 0,失败 = 错误码线程默认是可结合态(joinable)
获取自身 IDpthread_self获取当前线程 ID返回pthread_t无失败,线程内调用
等待线程结束pthread_join阻塞等待线程退出、回收资源成功 = 0只能等待 joinable 线程
设置分离态pthread_detach将线程改为分离态成功 = 0分离后不能 join,系统自动回收
线程退出pthread_exit终止当前线程可带返回值只退出线程,不退出进程
取消线程pthread_cancel发送取消请求成功 = 0不是立即杀死,看取消状态
设置取消状态pthread_setcancelstate允许 / 禁止被取消ENABLE / DISABLE默认允许取消
设置取消类型pthread_setcanceltype延时 / 异步取消DEFERRED / ASYNCHRONOUS默认延时取消(安全)
线程属性初始化pthread_attr_init初始化属性对象成功 = 0创建分离态线程必须用
设置分离属性pthread_attr_setdetachstate创建前指定分离 / 结合JOINABLE / DETACHED比 detach 更早设置
销毁属性pthread_attr_destroy释放属性资源成功 = 0使用完必须销毁

2、线程 生命周期流程图(文字版)

主线程 ↓ pthread_create ——→ 创建子线程(新建 → 就绪 → 运行) ↓ 子线程开始执行 ↓ 子线程运行中: - 可调用 pthread_self 获取自己ID - 可设置 setcancelstate / setcanceltype ↓ 子线程两种结束方式: 1. 正常结束 → return / pthread_exit 2. 被取消 → pthread_cancel(到达取消点退出) ↓ 线程结束后: ├── 若为 JOINABLE(默认)→ 必须 pthread_join 回收 └── 若为 DETACHED → 系统自动回收,无需 join

joinable 必须 join,否则资源泄漏
detached 不能 join,系统自动清理
cancel 只是发请求,不是立即杀死,默认延时取消

3、完整示例

#include <stdio.h> #include <pthread.h> #include <unistd.h> // 线程运行函数 void* func(void* arg) { int id = *(int*)arg; // 获取自身线程ID pthread_t tid = pthread_self(); printf("线程%d 运行,tid=%lu\n", id, (unsigned long)tid); // 设置允许取消(默认就是允许,演示用) pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); // 设置延时取消(默认,安全) pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); sleep(3); // 取消点 printf("线程%d 正常结束\n", id); return NULL; } int main() { pthread_t tid; int num = 1; // 1. 创建线程 pthread_create(&tid, NULL, func, &num); sleep(1); // 2. 取消线程 pthread_cancel(tid); // 3. 等待回收(必须join,因为默认joinable) void* ret; pthread_join(tid, &ret); if (ret == PTHREAD_CANCELED) printf("线程已被取消\n"); return 0; }
http://www.jsqmd.com/news/825934/

相关文章:

  • 12 - AI Native“基因测序法”:你的产品是“数字生命”还是“行尸走肉”?
  • 训练篇第7节:混合并行实战——以Megatron-LM和DeepSpeed为例剖析3D并行
  • ContextGit:为代码库注入结构化上下文,提升代码可追溯性与团队协作效率
  • 绝缘子缺陷检测数据集2148张VOC+YOLO格式
  • 中小企业云上安全从零搭建:低成本防护架构落地指南
  • Arm Ethos-U85 NPU架构解析与边缘AI优化实践
  • 使用go-ios运行fastbot
  • DNS优化实战:从运营商DNS到HttpDNS的进化之路
  • MySQL 登录报错排查:1045、2003 错误,新手快速解决
  • 软件交付质量与风险管理的关键指标与实践
  • 汽车电源管理系统:同步降压转换器与LDO技术解析
  • Flutter for OpenHarmony列表刷新加载实战
  • 从 LLM 到 Agent:Harness Engineering 的角色演变
  • 矢量图转换神器:5分钟将普通图片升级为无限放大的矢量图
  • (2)达梦数据库--SQl基础实践
  • 交货期约束平行机在线调度优化【附代码】
  • 05手写画布实现-鸿蒙PC端Electron开发
  • 2026年评价高的双法兰伸缩接头/双法兰限位伸缩接头深度厂家推荐 - 行业平台推荐
  • 数据库缓冲池优化:数组翻译技术的原理与实践
  • TestDisk与PhotoRec:免费开源的数据恢复双雄终极指南
  • 14 - AI新物种设计罗盘:从“填表”到“意图瞬移”的六把密钥
  • 纸箱破洞湿水检测数据集3322张VOC+YOLO格式
  • NoFences:你的Windows桌面整理革命,告别杂乱无章的终极方案
  • 通过用量看板直观对比不同模型调用的延迟与花费
  • AI视频工业化革命(Sora 2×TikTok创作闭环全拆解):实测单日产出47条自然流量破10w+视频的私有工作流
  • 国内外AI都搞不定----看来要我出马了
  • UVA10341 Solve It 题解
  • 蜂群协议深度解析:构建高弹性分布式系统的核心原理与实践
  • Day08 用户下单
  • 基于LLM视觉的智能家居自动化:ha-llmvision集成部署与实战指南