1、线程的基本概念
线程是进程内的最小执行单元,也被称为轻量级进程(LWP)。一个进程可以包含多个线程,所有线程共享进程的核心资源,同时拥有自己独立的执行上下文(如程序计数器、寄存器、栈)。
2、线程和进程的区别
| 维度 | 进程 | 线程 |
|---|---|---|
| 资源分配 | 操作系统资源分配的最小单元(独立的地址空间、文件描述符、内存等) | 操作系统CPU任务调度的最小单元(不独立分配资源,共享所属进程的资源) |
| 地址空间 | 每个进程有独立的虚拟地址空间 | 无独立地址空间,共享进程的文本区、数据区、堆区、文件描述符、信号处理等;仅独享栈区、寄存器上下文、程序计数器 (PC)、线程 ID |
| 独立性 | 完全独立,一个进程崩溃不影响其他进程 | 高度依赖进程,一个线程崩溃会导致整个进程终止(进程内所有线程退出) |
| 通信 / 同步 | 需借助 IPC(管道、消息队列、共享内存等),成本高 | 直接读写共享变量即可通信,需通过锁 / 信号量等机制解决同步问题 |
| 切换开销 | 大(需切换地址空间、页表等) | 小(仅切换执行上下文,无需修改地址空间) |
3、线程的创建
- 调用
pthread_create创建线程时,系统会为该线程分配独立的栈空间 (Linux 默认栈大小为 8MB,可通过ulimit -s查看 / 修改); - 线程的文本区(代码)、数据区(全局 / 静态变量)、堆区(动态分配内存)、文件描述符等,均复用所属进程的资源;
- 线程创建成功后,新线程与主线程并发执行(无固定执行顺序,由 CPU 调度决定)。
4、线程的调度
- 线程调度规则基本等同于进程调度(支持抢占式调度、时间片轮转、优先级调度等);
- 宏观上:多个线程 "同时" 执行(并行),因为 CPU 多核或快速切换;
- 微观上:单核 CPU 中,同一时刻只有一个线程在执行(串行),依赖 CPU 时间片切换实现 "伪并行"。
5、线程的消亡
- 等同于进程的消亡,线程消亡需要回收线程空间
- 线程消亡的触发条件:
- 线程函数执行完毕(隐式退出);
- 调用
pthread_exit主动退出; - 进程终止(所有线程随之消亡);
- 被
pthread_cancel取消(被动退出)。
- 线程消亡后必须回收其资源(线程空间):
- 若未回收,线程会变成 "僵尸线程",占用系统资源(类似僵尸进程);
- 回收方式:
pthread_join(阻塞回收)、pthread_detach(分离线程,系统自动回收)。
6、多线程和多进程优缺点
| 特性 | 多进程 | 多线程 |
|---|---|---|
| 安全性(稳定性) | 高:地址空间独立,一个进程崩溃不影响其他进程 | 低:共享进程资源,一个线程崩溃导致整个进程终止;需处理资源竞争 / 线程安全问题 |
| 执行效率 | 低(切换 / 通信开销大)因为切换进程任务时需要映射不同的物理地址空间,增大系统开销 | 高(切换 / 通信开销小)因为在同一进程空间内部切换不同的任务 |
| 编程复杂度 | 低(无需处理资源竞争) | 高(需处理同步 / 互斥问题) |
| 资源占用 | 高(每个进程独立占资源) | 低(共享进程资源) |
| 适用场景 | 独立任务、对稳定性要求高(如后台服务、多程序运行) | 高并发、数据共享频繁(如网络服务、GUI 程序) |
| 资源竞争 | 无:地址空间独立,无共享资源竞争 | 有:共享数据区 / 堆区,需通过互斥锁、条件变量等防止 "竞态条件" |
| 通信难度 | 高:需 IPC(管道、共享内存等)多进程通信不方便,因为空间独立,没有共享空间 | 低:直接访问共享变量(全局 / 堆),仅需同步机制(锁 / 信号量)多线程通信非常方便,多线程数据区、堆区共享 |
7、线程相关函数接口
7.1 pthread_create
- 函数原型
cpp
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
- 功能:创建一个新线程,线程启动后执行
start_routine函数; - 参数:
thread:输出参数,存放新线程的 ID(注意:可用 %#xpthread_self()获取id);attr:线程属性(如栈大小、分离状态),默认属性传NULL;start_routine:线程函数入口(函数指针),必须是void* (*)(void*)类型(返回值和参数均为void*);arg:传给线程函数的参数(若传递栈变量地址,需确保变量生命周期长于线程(避免野指针);
- 返回值:成功返回 0,失败返回错误码 (不是 errno,需用
strerror()解析,如perror不适用); - 编译注意:必须链接 pthread 库,编译命令加
-lpthread(如gcc test.c -o test -lpthread)。
7.2 pthread_exit
- 函数原型
cpp
void pthread_exit(void *retval);
- 功能:主动终止当前线程,且不会影响同进程的其他线程;
- 参数:
retval:线程退出的返回值(可被pthread_join()获取),若无需返回值传NULL; - 注意:主线程调用
pthread_exit()仅终止自身,子线程仍会继续执行;若主线程直接return,则整个进程终止。 - 返回值:缺省
7.3 pthread_join
- 函数原型
cpp
int pthread_join(pthread_t thread, void **retval);
- 功能:阻塞等待指定线程终止,并回收其资源(避免僵尸线程);
- 参数:
thread:要回收的线程 ID;retval:输出参数,存放线程的返回值(即pthread_exit()的retval),无需获取则传NULL;
- 返回值:成功返回 0,失败返回错误码(如线程已分离、线程不存在);
- 核心作用:
- 阻塞回收线程空间资源;
- 实现线程同步(主线程等待子线程执行完毕)。
- 注意:一个线程只能被一个
pthread_join回收,重复调用会失败。
示例(创建 + 回收线程):
cpp
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
void *thread_func(void *arg) {
char *msg = (char *)arg;
printf("子线程ID:%lu,收到参数:%s\n", (unsigned long)pthread_self(), msg);
// 堆内存,手动分配,主线程回收
int *ret_val = (int *)malloc(sizeof(int));
*ret_val = 100;
pthread_exit((void *)ret_val); // 返回堆地址
}
int main() {
pthread_t tid;
char *msg = "Hello Thread";
int err;
err = pthread_create(&tid, NULL, thread_func, (void *)msg);
if (err != 0) {
printf("创建线程失败:%s\n", strerror(err));
exit(1);
}
printf("主线程:创建的子线程ID为%lu\n", (unsigned long)tid);
void *ret;
err = pthread_join(tid, &ret);
if (err != 0) {
printf("回收线程失败:%s\n", strerror(err));
exit(1);
}
// 访问堆数据
printf("主线程:子线程退出,返回值为%d\n", *(int *)ret);
free(ret); // 必须手动释放堆内存,避免内存泄漏
return 0;
}