线程概念与控制要点总结
一、先建立正确的"线程观"
- 进程 与 线程 :进程是资源分配的基本单位,线程是 CPU 调度的基本单位。同一进程的多个线程共享地址空间、全局变量、堆、打开的文件描述符 ,但各自有独立的栈、寄存器和线程 ID。
- 为什么用线程 :比进程更轻量 (创建/切换开销小)、更适合密集并发任务,是高性能后台、网络服务、gui 应用的基础。面试常考"进程和线程的区别"------答共享内存 + 独立栈 + 调度单位。
二、线程的生命周期与核心 API
| 阶段 | 函数 | 作用 / 调用时机 |
|---|---|---|
| 创建 | pthread_create(pthread_t *tid, attr, void *(*start)(void*), void *arg) |
启动新线程,入口函数签名固定为 void* func(void*) |
| 终止 | pthread_exit(void *retval) |
线程自行退出,返回退出码 |
| 结束自己 | pthread_exit(NULL) |
入口函数 return 相当于它 |
| 等待 | pthread_join(tid, void **retval) |
阻塞等待某线程结束,并回收其返回值/资源 |
| 分离 | pthread_detach(tid) |
使线程结束时自动释放资源,无需 join |
| 取 ID | pthread_self() |
返回当前线程 tid |
| 比较 | pthread_equal(a, b) |
判断两个 tid 是否相同(不能简单用 ==) |
| 取消 | pthread_cancel(tid) |
请求取消线程 |
关键点 :
pthread_create的第 1 个参数是int类型(pthread_create(&tid, ...)传的是&tid),编译/链接需要-pthread或-lpthread。
三、编译与链接
- 用了 pthread 但不加链接选项 会报
undefined reference to 'pthread_create'。 - 解决:
gcc xxx.c -o xxx -pthread(推荐,同时定义宏)或-lpthread。 - 说明"编译"和"链接"是两个阶段:
#include <pthread.h>只提供声明,真正实现要从libpthread里链接进来。
四、线程入口函数与传参
- 入口函数签名必须是:
void *func(void *arg)。 - 不能把"局部变量"直接传给线程 (课件强调的典型错误):线程可能在线程函数结束后才开始运行,局部变量已失效 → 未定义行为。正确做法:
- 放到全局变量;
- 放进 malloc 的动态内存,线程用完再 free;
- 把参数塞进结构体、把结构体指针传进去;需要多个值就用结构体。
五、线程同步
- 临界区 / Critical Section:多个线程同时读写共享数据会造成竞争(race condition)、数据竞争(data race)。需要加锁保护。
- 互斥锁
pthread_mutex_t:pthread_mutex_init / lock / unlock / destroy- 原则:哪个线程加锁,哪个线程释放 ;加锁范围尽量小;注意死锁(多见于多个锁嵌套、顺序不一致)。
- 条件变量
pthread_cond_t:pthread_cond_wait / signal / broadcast- 用于"等待某个条件成立",常与互斥锁配合;
wait会先释放锁、唤醒后再重新加锁(课件中会强调"wait 必须配合 mutex")。
- 线程安全(thread-safe) :
malloc/free/标准库部分函数本身是线程安全的(glibc 内部有锁),但"线程安全的库"不等于"你的程序无竞争"------仍要自己加锁保护共享结构。
六、线程与进程的经典考点
- 线程共享内存、进程隔离内存。
- 面试题:"一个进程里多个线程,
fork会怎样?"、"线程出现崩溃会不会带垮整个进程?"(答:同一进程内线程共享地址空间,未处理的错误/非法访问会连累进程)。