# 9 pthread多线程基础 ## 9.1 完整概念讲解 ### 线程基本概念 - **轻量级进程(LWP)**:线程是CPU调度的基本单位,进程是资源分配的基本单位 - **共享地址空间**:同一进程内的线程共享代码段、数据段、堆空间,但各自拥有独立的栈和寄存器上下文 - **线程ID vs 进程ID**: - 进程ID(pid_t)在整个系统中唯一 - 线程ID(pthread_t)在进程内唯一,不同进程中可能相同 - 获取方式:`getpid()` vs `pthread_self()` ### 为什么需要多线程 1. **并发执行**:多个任务同时推进,提高CPU利用率 2. **资源共享**:线程间共享内存,无需复杂的IPC机制 3. **响应性**:主线程处理UI/交互,子线程执行耗时任务 4. **经济性**:创建/切换开销远小于进程 ### POSIX线程标准 - IEEE POSIX 1003.1c标准(1995年) - Linux实现:NPTL(Native POSIX Thread Library) - 头文件:`` - 库:`libpthread`(编译时加`-pthread`) ### 线程 vs 进程对比 | 特性 | 线程 | 进程 | | -------- | ------------------ | ---------------------- | | 创建开销 | 小(~10μs) | 大(~100μs) | | 切换开销 | 小(~1μs) | 大(~10μs) | | 通信方式 | 共享内存(直接) | IPC(管道/共享内存等) | | 资源占用 | 共享地址空间 | 独立地址空间 | | 安全性 | 低(数据竞争风险) | 高(天然隔离) | | 典型应用 | 任务并行、IO重叠 | 服务隔离、安全边界 | ## 9.2 核心API/语法 ### pthread_create - 创建线程 ```c int pthread_create( pthread_t *thread, // 输出:线程ID const pthread_attr_t *attr, // 属性(NULL为默认) void *(*start_routine)(void*), // 线程函数 void *arg // 传递给线程函数的参数 ); // 返回:成功返回0,失败返回错误码(不是-1) ``` ### pthread_join - 等待线程结束 ```c int pthread_join( pthread_t thread, // 要等待的线程ID void **retval // 输出:线程函数的返回值 ); // 返回:成功返回0 // 阻塞直到目标线程结束 ``` ### pthread_detach - 分离线程 ```c int pthread_detach(pthread_t thread); // 分离后线程结束后自动回收资源,不能用pthread_join // 常用:pthread_detach(pthread_self()) 在线程函数内部自我分离 ``` ### pthread_exit - 退出线程 ```c void pthread_exit(void *retval); // 终止当前线程,retval作为返回值 // 注意:不要返回局部变量地址 ``` ### 线程函数原型 ```c void* thread_func(void* arg) { // 类型转换使用实际类型 int *data = (int*)arg; // ... int *result = malloc(sizeof(int)); *result = 42; return (void*)result; // 返回堆内存或静态变量 } ``` ## 9.3 代码示例 ### 基础示例:创建线程并等待 ```c #include #include #include void* thread_func(void* arg) { int id = *(int*)arg; printf("线程 %d 运行中, TID: %lu\n", id, pthread_self()); sleep(1); int *result = malloc(sizeof(int)); *result = id * 100; return (void*)result; } int main() { pthread_t t1, t2; int id1 = 1, id2 = 2; void *ret1, *ret2; if (pthread_create(&t1, NULL, thread_func, &id1) != 0) { perror("pthread_create"); return 1; } if (pthread_create(&t2, NULL, thread_func, &id2) != 0) { perror("pthread_create"); return 1; } pthread_join(t1, &ret1); pthread_join(t2, &ret2); printf("线程1返回: %d\n", *(int*)ret1); printf("线程2返回: %d\n", *(int*)ret2); free(ret1); free(ret2); return 0; } ``` **编译命令:** ```bash gcc -o pthread_basic pthread_basic.c -pthread ./pthread_basic ``` ### 自我分离线程示例 ```c #include #include void* detached_thread(void* arg) { printf("分离线程运行中\n"); sleep(2); printf("分离线程结束\n"); pthread_detach(pthread_self()); // 自我分离 return NULL; } int main() { pthread_t tid; pthread_create(&tid, NULL, detached_thread, NULL); // 主线程不需要pthread_join sleep(1); printf("主线程结束\n"); return 0; } ``` ### 嵌入式场景:任务处理 ```c #include #include #include // UI线程(主线程) void ui_task() { for (int i = 0; i < 5; i++) { printf("[UI] 刷新界面 %d\n", i); usleep(500000); } } // 后台数据处理线程 void* data_task(void* arg) { int sensor_id = *(int*)arg; for (int i = 0; i < 10; i++) { printf("[DATA] 读取传感器%d: %d\n", sensor_id, i); usleep(200000); } return NULL; } int main() { pthread_t data_tid; int sensor = 1; // 启动后台数据采集线程 pthread_create(&data_tid, NULL, data_task, &sensor); // 主线程处理UI ui_task(); pthread_join(data_tid, NULL); printf("所有任务完成\n"); return 0; } ``` ## 9.4 注意事项与易错点 ### 1. 返回值陷阱 ```c // 错误:返回局部变量地址(栈内存已释放) void* bad_thread(void* arg) { int result = 42; return (void*)&result; // 未定义行为! } // 正确:使用堆内存或静态变量 void* good_thread(void* arg) { int *result = malloc(sizeof(int)); *result = 42; return (void*)result; // OK } ``` ### 2. 参数传递 ```c // 错误:传递局部变量地址(主线程可能已修改) int main() { int val = 100; pthread_create(&tid, NULL, thread_func, &val); val = 200; // 可能在线程读取前被修改 } // 正确:动态分配或确保生命周期 int *param = malloc(sizeof(int)); *param = 100; pthread_create(&tid, NULL, thread_func, param); ``` ### 3. 常见错误码 | 错误码 | 含义 | 常见原因 | | ------- | ---------- | -------------- | | EAGAIN | 资源不足 | 线程数达到上限 | | EINVAL | 无效参数 | 属性设置错误 | | ESRCH | 找不到线程 | 线程已退出 | | EDEADLK | 死锁 | 自己等待自己 | ### 4. 其他注意事项 - 不要忽略返回值,用`-pthread`编译确保链接 - 全局变量需加锁保护(后续讲mutex) - `pthread_cancel`慎用,可能导致资源泄漏 - 线程栈大小默认8MB,可通过属性调整 ## 9.5 面试要点(3-5个Q&A) **Q1:线程和进程的区别?为什么多线程比多进程更快?** > 进程是资源分配单位,线程是调度单位。同进程线程共享地址空间,创建/切换无需拷贝页表、刷新TLB,因此开销小10-100倍。通信也更简单,直接读写共享内存。 _*Q2:pthread_create的arg参数为什么是void*?如何安全传递复杂数据?_* > void*是C的泛型机制,允许传递任意类型。安全做法:传递指针指向堆内存或结构体,确保生命周期覆盖线程执行期。避免传递栈上的局部变量地址。 **Q3:pthread_join和pthread_detach的区别?什么时候用哪个?** > join:主线程阻塞等待子线程结束,获取返回值,资源手动回收。 > detach:子线程结束后自动回收,不能join。 > 选择:需要结果或控制用join;fire-and-forget任务用detach。 **Q4:如果线程函数访问全局变量,会有什么问题?如何解决?** > 多线程并发访问共享变量导致数据竞争(race condition),结果不可预测。解决:使用互斥锁(pthread_mutex)、读写锁、原子操作或线程局部存储(__thread)。 **Q5:嵌入式系统中多线程的典型应用场景?** > - UI线程 + 数据采集线程(实时性) > - 网络通信线程 + 业务处理线程 > - 多核并行计算(矩阵运算等) > - Watchdog线程监控主程序