线程相关知识
线程是进程内的一条独立执行流,是操作系统调度 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) |
| 获取自身 ID | pthread_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 → 系统自动回收,无需 joinjoinable 必须 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; }