【Linux】线程概念与控制(二):POSIX线程库API实战
文章目录
Linux线程概念与控制(二):POSIX线程库API实战
💬 承接上文:在上一篇中,我们深入理解了线程的本质、虚拟地址空间的分页机制以及线程与进程的区别。理论基础已经打牢,现在是时候动手实践了!本篇将带你掌握Linux下多线程编程的核心API,从线程创建到终止、等待、分离,每个API都配合完整可运行的代码示例,让你真正学会多线程编程!
👍 学习目标:掌握POSIX线程库的使用、理解pthread_t的本质、学会创建和控制线程、理解LWP与线程ID的区别、掌握线程的三种终止方式、理解线程等待和分离的概念。
🚀 实战为主:本篇以代码实战为主,每个知识点都有完整示例,建议边看边敲代码!
一、POSIX线程库入门
1.1 什么是POSIX线程库
POSIX线程(pthread):
- POSIX: Portable Operating System Interface(可移植操作系统接口)
- pthread: POSIX thread的缩写
- 是一套跨平台的线程API标准
特点:
✓ 跨平台:Linux、Unix、MacOS都支持
✓ 标准化:遵循POSIX标准
✓ 功能完善:涵盖线程创建、同步、销毁等全流程
在Linux下,与线程相关的函数构成了一个完整的系列,绝大多数函数名都以pthread_开头,这让API非常容易识别和记忆。
1.2 如何使用pthread库
使用pthread库需要链接pthread库
# 错误的编译方式(会报undefined reference错误)
gcc test.c -o test
# 正确的编译方式(需要-lpthread选项)
gcc test.c -o test -lpthread
# 说明:
# -l 表示链接库
# pthread 是库的名称
# 实际链接的库文件是 libpthread.so
步骤3:编写线程代码
// 这是一个最简单的多线程程序框架
#include <stdio.h>
#include <pthread.h>
void* thread_func(void* arg) {
printf("子线程运行中\n");
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
pthread_join(tid, NULL);
printf("主线程结束\n");
return 0;
}
1.3 pthread的错误处理机制
pthread库的错误处理与传统函数不同,这点需要特别注意:
传统函数的错误处理:
// 传统POSIX函数(如open、read等)
int fd = open("file.txt", O_RDONLY);
if (fd == -1) {
// 失败返回-1
perror("open"); // 通过全局变量errno获取错误信息
}
pthread函数的错误处理:
// pthread函数
int ret = pthread_create(&tid, NULL, thread_func, NULL);
if (ret != 0) {
// 不设置全局变量errno
fprintf(stderr, "pthread_create: %s\n", strerror(ret));
}
📌 核心区别:
传统函数:
- 成功返回0或正值
- 失败返回-1
- 设置全局变量errno
pthread函数:
- 成功返回0
- 失败返回错误码(正整数)
- 不设置errno(避免多线程冲突)
- 通过返回值判断错误
原因:
- errno是全局变量,多线程共享会冲突
- pthread提供了线程内的errno副本
- 但推荐直接使用返回值判断错误(开销更小)
正确的错误检查示例:
#include <stdio.h>
#include <string.h> // strerror
#include <pthread.h>
void* thread_func(void* arg) {
return NULL;
}
int main() {
pthread_t tid;
int ret;
// 正确的错误检查方式
ret = pthread_create(&tid, NULL, thread_func, NULL);
if (ret != 0) {
fprintf(stderr, "pthread_create failed: %s\n", strerror(ret));
return 1;
}
pthread_join(tid, NULL);
return 0;
}
二、线程创建pthread_create
2.1 函数原型详解
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void*),
void *arg);
参数说明:
参数1: pthread_t *thread
- 输出参数,用于返回新线程的ID
- pthread_t是一个不透明类型(实现相关)
- 在Linux NPTL实现中,pthread_t是unsigned long类型
参数2: const pthread_attr_t *attr
- 线程属性,设置线程的各种特性
- 传NULL表示使用默认属性
- 默认属性:joinable(可join)、非分离、默认栈大小等
参数3: void *(*start_routine)(void*)函数指针,指向线程要执行的函数
- 函数签名必须是: void* func(void*)
- 参数和返回值都是void*,可以传递任意类型指针
参数4: void *arg
- 传递给start_routine的参数
- 可以传递任意类型的指针
- 如果不需要参数,传NULL
返回值:
- 成功返回0
- 失败返回错误码(正整数)
2.2 第一个多线程程序
让我们写一个完整的多线程程序,创建一个新线程,让它和主线程交替打印信息:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
// 子线程要执行的函数
void* thread_routine(void* arg) {
int i;
for (i = 0; i < 5; i++) {
printf("I am thread 1\n");
sleep(1);
}
return NULL;
}
int main() {
pthread_t tid;
int ret;
// 创建新线程
ret = pthread_create(&tid, NULL, thread_routine, NULL);
if (ret != 0) {
fprintf(stderr, "pthread_create: %s\n", strerror(ret));
return 1;
}
// 主线程继续执行
int i;
for (i = 0; i < 5; i++) {
printf("I am main thread\n");
sleep(1);
}
return 0;
}
编译运行:
I am main thread
I am thread 1
I am main thread
I am thread 1
I am main thread
I am thread 1
I am main thread
I am thread 1
I am main thread
- 主线程和子线程交替打印(顺序可能不同)
- 两个线程并发执行,互不阻塞
- 主线程结束后,进程就终止了(子线程也被强制终止)
问题: 为什么主线程结束,子线程也结束了?
答: 主线程return相当于调用exit(),会终止整个进程
解决: 使用pthread_join等待子线程(后面讲解)
2.3 pthread_self获取线程ID
每个线程都可以通过pthread_self获取自己的线程ID:
pthread_t pthread_self(void);
返回值:
返回调用线程的线程ID(pthread_t类型)
示例代码:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* thread_routine(void* arg) {
printf("子线程ID: %lu\n", pthread_self());
sleep(1);
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_routine, NULL);
printf("主线程ID: %lu\n", pthread_self());
printf("通过tid获取的子线程ID: %lu\n", tid);
sleep(2); // 等待子线程打印
return 0;
}
运行结果:
$ ./thread2
主线程ID: 140248607516480
通过tid获取的子线程ID: 140248607512320
子线程ID: 140248607512320
📌 理解pthread_t:
问题: pthread_t到底是什么?
答案: pthread_t的实现由POSIX库决定
- 在Linux NPTL实现中,pthread_t是unsigned long类型
- 它的值是一个虚拟地址
- 这个地址指向线程控制块(TCB)的位置
- TCB存储了线程的各种信息(栈地址、寄存器、优先级等)
重要: pthread_t是进程级唯一的,不是系统级唯一的!
- 不同进程可能有相同的pthread_t值
- 但在同一个进程内,pthread_t是唯一的
2.4 LWP:真正的线程ID
2.4.1 pthread_t vs LWP
pthread_t:
- pthread库维护的线程ID
- 作用域:进程级
- 类型:unsigned long(Linux NPTL)
- 本质:线程控制块的虚拟地址
- 用途:pthread库的API参数
LWP (Light Weight Process):
- 内核维护的轻量级进程ID
- 作用域:系统级(全局唯一)
- 类型:pid_t(整数)
- 本质:内核调度的实体
- 用途:内核调度、ps命令查看
2.4.2 查看LWP
我们可以通过ps命令查看线程的LWP:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* thread_routine(void* arg) {
while (1) {
printf("子线程运行中, pthread_t = %lu\n", pthread_self());
sleep(2);
}
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_routine, NULL);
while (1) {
printf("主线程运行中, pthread_t = %lu\n", pthread_self());
sleep(2);
}
return 0;
}
运行程序,然后查看线程信息:
# 运行程序
$ ./thread3 &
[1] 12345
# 查看线程信息(-L选项显示线程)
$ ps -aL | head -1 && ps -aL | grep thread3
PID LWP TTY TIME CMD
12345 12345 pts/0 00:00:00 thread3 ← 主线程
12345 12346 pts/0 00:00:00 thread3 ← 子线程
# 说明:
# PID: 进程ID(相同,都是12345)
# LWP: 轻量级进程ID(不同,12345和12346)
# 主线程的LWP等于PID
# 子线程的LWP由内核分配
📌 核心理解:
1. Linux下线程就是轻量级进程
2. 内核不区分线程和进程,都用task_struct表示
3. 主线程的LWP == PID
4. 子线程的LWP != PID,但属于同一个进程组
用图示理解:
进程12345
├─ LWP 12345 (主线程, pthread_t=0x7f1234567000)
└─ LWP 12346 (子线程, pthread_t=0x7f1234563000)
内核眼中: 两个task_struct,都可以独立调度
pthread库眼中: 同一个进程的两个线程,共享地址空间
2.5 线程参数传递
2.5.1 传递单个参数
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* thread_routine(void* arg) {
int num = *(int*)arg; // 转换void*为int*,再解引用
printf("子线程收到参数: %d\n", num);
return NULL;
}
int main() {
pthread_t tid;
int data = 100;
// 传递data的地址
pthread_create(&tid, NULL, thread_routine, &data);
sleep(1); // 等待子线程执行
return 0;
}
⚠️ 危险示例(不要这样做):
void* thread_routine(void* arg) {
int num = *(int*)arg;
printf("num = %d\n", num);
return NULL;
}
int main() {
pthread_t tid;
int i;
for (i = 0; i < 5; i++) {
// 错误! 传递局部变量i的地址
pthread_create(&tid, NULL, thread_routine, &i);
}
sleep(1);
return 0;
}
// 问题: 所有线程共享变量i的地址
// 当循环执行时,i的值会变化
// 可能所有线程都打印5
// 解决方法1: 为每个线程分配独立的参数
// 解决方法2: 直接传值(把整数转为指针)
2.5.2 传递多个参数
#include <stdio.h>
#include <pthread.h>
#include <string.h>
// 定义参数结构体
struct thread_arg {
int id;
char name[32];
};
void* thread_routine(void* arg) {
struct thread_arg* param = (struct thread_arg*)arg;
printf("线程%d: %s\n", param->id, param->name);
return NULL;
}
int main() {
pthread_t tid1, tid2;
struct thread_arg arg1 = {1, "Thread-A"};
struct thread_arg arg2 = {2, "Thread-B"};
pthread_create(&tid1, NULL, thread_routine, &arg1);
pthread_create(&tid2, NULL, thread_routine, &arg2);
sleep(1);
return 0;
}
三、线程终止的三种方式
线程可以通过三种方式终止:
方式1: 从线程函数return
方式2: 调用pthread_exit主动退出
方式3: 被其他线程调用pthread_cancel取消
注意: main函数return会终止整个进程!
3.1 方式1:return返回
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
void* thread_routine(void* arg) {
printf("子线程开始执行\n");
int* result = (int*)malloc(sizeof(int));
*result = 100;
printf("子线程即将返回\n");
return (void*)result; // 返回值可以被pthread_join获取
}
int main() {
pthread_t tid;
void* ret_val;
pthread_create(&tid, NULL, thread_routine, NULL);
// 等待线程(后面详细讲解)
pthread_join(tid, &ret_val);
printf("子线程返回值: %d\n", *(int*)ret_val);
free(ret_val);
return 0;
}
📌 注意事项:
1. 不要返回栈上变量的地址!
void* thread_routine(void* arg) {
int result = 100;
return &result; // 错误! result是栈上变量,函数返回后失效
}
2. 可以返回:
✓ malloc分配的堆内存地址
✓ 全局变量的地址
✓ 静态变量的地址
✓ 整数值(强制转换为指针,不解引用)
3.2 方式2:pthread_exit退出
void pthread_exit(void *value_ptr);
参数:
value_ptr: 线程的退出状态
- 可以被pthread_join获取
- 不要指向栈上的局部变量
示例代码:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
void* thread_routine(void* arg) {
printf("子线程执行中...\n");
int* result = (int*)malloc(sizeof(int));
*result = 200;
printf("子线程调用pthread_exit退出\n");
pthread_exit((void*)result); // 主动退出
// 后面的代码不会执行
printf("这句不会打印\n");
return NULL;
}
int main() {
pthread_t tid;
void* ret_val;
pthread_create(&tid, NULL, thread_routine, NULL);
pthread_join(tid, &ret_val);
printf("子线程退出码: %d\n", *(int*)ret_val);
free(ret_val);
return 0;
}
pthread_exit vs return:
相同点:
- 都能终止线程
- 都能设置退出状态
不同点:
1. pthread_exit可以在任何函数中调用
- 不一定在线程函数中
- 可以在线程调用的任何子函数中
2. return只能在线程函数中使用
- 如果在子函数中return,只是退出子函数
- 不会终止线程
示例:
void helper() {
pthread_exit(NULL); // OK,终止线程
return; // 只是退出helper函数
}
void* thread_routine(void* arg) {
helper();
printf("如果helper用return,这里会执行\n");
printf("如果helper用pthread_exit,这里不会执行\n");
return NULL;
}
⚠️ main函数中的pthread_exit:
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_routine, NULL);
pthread_exit(NULL); // 主线程退出,但进程不终止!
// 子线程继续运行
}
// 对比:
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_routine, NULL);
return 0; // 进程终止,所有线程都终止!
}
3.3 方式3:pthread_cancel取消线程
int pthread_cancel(pthread_t thread);
参数:
thread: 要取消的线程ID
返回值:
- 成功返回0
- 失败返回错误码
示例代码:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* thread_routine(void* arg) {
int i = 0;
while (1) {
printf("子线程运行中... %d\n", i++);
sleep(1);
}
return NULL;
}
int main() {
pthread_t tid;
void* ret_val;
pthread_create(&tid, NULL, thread_routine, NULL);
sleep(3); // 让子线程运行3秒
printf("主线程取消子线程\n");
pthread_cancel(tid);
pthread_join(tid, &ret_val);
if (ret_val == PTHREAD_CANCELED) {
printf("子线程被取消了\n");
}
return 0;
}
运行结果:
$ ./thread_cancel
子线程运行中... 0
子线程运行中... 1
子线程运行中... 2
主线程取消子线程
子线程被取消了
📌 取消点(Cancellation Point):
问题: pthread_cancel是立即生效吗?
答: 不是! 线程只有到达取消点才会真正被取消
取消点包括:
- sleep、read、write等阻塞系统调用
- pthread_testcancel(主动设置取消点)
如果线程一直在计算(没有调用阻塞函数),可能无法被取消:
void* thread_routine(void* arg) {
while (1) {
// 纯计算,没有取消点
}
return NULL; // 永远执行不到
}
解决: 手动设置取消点
void* thread_routine(void* arg) {
while (1) {
// 计算...
pthread_testcancel(); // 检查是否被取消
}
return NULL;
}
四、线程等待pthread_join
4.1 为什么需要线程等待
问题1: 线程资源泄漏
- 线程退出后,其资源(栈、TCB等)不会自动释放
- 需要其他线程pthread_join来回收资源
- 否则会造成资源泄漏
问题2: 获取线程退出状态
- 有时需要知道线程的返回值
- pthread_join可以获取线程的退出状态
问题3: 同步
- 主线程可能需要等待子线程完成某个任务
- pthread_join提供了一种同步机制
4.2 pthread_join函数
int pthread_join(pthread_t thread, void **value_ptr);
参数:
thread: 要等待的线程ID
value_ptr: 输出参数,存储线程的返回值
- 如果不关心返回值,可以传NULL
- 它是二级指针,指向一个void*指针
返回值:
- 成功返回0
- 失败返回错误码
函数行为:
1. 调用pthread_join的线程会阻塞
2. 直到目标线程退出
3. 回收目标线程的资源
4. 获取目标线程的退出状态(如果value_ptr非NULL)
4.3 获取线程返回值
4.3.1 return返回值
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void* thread1(void* arg) {
printf("thread1 returning...\n");
int* p = (int*)malloc(sizeof(int));
*p = 100;
return (void*)p;
}
int main() {
pthread_t tid;
void* ret;
pthread_create(&tid, NULL, thread1, NULL);
pthread_join(tid, &ret);
printf("thread1 return code: %d\n", *(int*)ret);
free(ret);
return 0;
}
4.3.2 pthread_exit退出值
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void* thread2(void* arg) {
printf("thread2 exiting...\n");
int* p = (int*)malloc(sizeof(int));
*p = 200;
pthread_exit((void*)p);
}
int main() {
pthread_t tid;
void* ret;
pthread_create(&tid, NULL, thread2, NULL);
pthread_join(tid, &ret);
printf("thread2 exit code: %d\n", *(int*)ret);
free(ret);
return 0;
}
4.3.3 pthread_cancel的返回值
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* thread3(void* arg) {
while (1) {
printf("thread3 running...\n");
sleep(1);
}
return NULL;
}
int main() {
pthread_t tid;
void* ret;
pthread_create(&tid, NULL, thread3, NULL);
sleep(3);
pthread_cancel(tid);
pthread_join(tid, &ret);
if (ret == PTHREAD_CANCELED) {
printf("thread3 was canceled\n");
}
return 0;
}
4.4 完整示例:三种终止方式
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
// 线程1: return返回
void* thread1(void* arg) {
printf("thread1 returning...\n");
int* p = (int*)malloc(sizeof(int));
*p = 100;
return (void*)p;
}
// 线程2: pthread_exit退出
void* thread2(void* arg) {
printf("thread2 exiting...\n");
int* p = (int*)malloc(sizeof(int));
*p = 200;
pthread_exit((void*)p);
}
// 线程3: 等待被cancel
void* thread3(void* arg) {
while (1) {
printf("thread3 running...\n");
sleep(1);
}
return NULL;
}
int main() {
pthread_t tid1, tid2, tid3;
void* ret;
// 测试return
printf("=== 测试return ===\n");
pthread_create(&tid1, NULL, thread1, NULL);
pthread_join(tid1, &ret);
printf("thread1 return code: %d\n", *(int*)ret);
free(ret);
// 测试pthread_exit
printf("\n=== 测试pthread_exit ===\n");
pthread_create(&tid2, NULL, thread2, NULL);
pthread_join(tid2, &ret);
printf("thread2 exit code: %d\n", *(int*)ret);
free(ret);
// 测试pthread_cancel
printf("\n=== 测试pthread_cancel ===\n");
pthread_create(&tid3, NULL, thread3, NULL);
sleep(3);
pthread_cancel(tid3);
pthread_join(tid3, &ret);
if (ret == PTHREAD_CANCELED) {
printf("thread3 was canceled\n");
}
return 0;
}
编译运行:
$ gcc thread_exit.c -o thread_exit -lpthread
$ ./thread_exit
=== 测试return ===
thread1 returning...
thread1 return code: 100
=== 测试pthread_exit ===
thread2 exiting...
thread2 exit code: 200
=== 测试pthread_cancel ===
thread3 running...
thread3 running...
thread3 running...
thread3 was canceled
4.5 pthread_join的注意事项
注意1: 只能join一次
- 同一个线程只能被join一次
- 多次join同一个线程会导致未定义行为
注意2: 不能join自己
- 线程不能join自己,否则会死锁
注意3: 不能join已分离的线程
- 分离线程(detached)不能被join
- 会返回EINVAL错误
注意4: join是阻塞操作
- 调用线程会一直阻塞,直到目标线程退出
- 如果目标线程一直运行,调用线程就一直等待
五、线程分离pthread_detach
5.1 什么是线程分离
默认情况下,线程是joinable的:
- 线程退出后,资源不会立即释放
- 需要其他线程调用pthread_join回收资源
- 如果不join,会造成资源泄漏
分离线程(detached thread):
- 线程退出后,资源自动释放
- 不需要也不能被join
- 适用于不关心线程退出状态的场景
形象比喻:
joinable线程:
- 就像临时工,工作完成后需要老板(其他线程)来结算工资
- 如果老板不来结算(join),临时工就一直占着工位(资源泄漏)
detached线程:
- 就像自由职业者,工作完成后自己走人,自动清理工位
- 不需要老板结算(不能join)
5.2 pthread_detach函数
int pthread_detach(pthread_t thread);
参数:
thread: 要分离的线程ID
- 可以是其他线程的ID
- 也可以是自己的ID(通过pthread_self()获取)
返回值:
- 成功返回0
- 失败返回错误码
5.3 线程分离示例
5.3.1 线程自己分离
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* thread_routine(void* arg) {
// 线程自己分离
pthread_detach(pthread_self());
printf("子线程运行中...\n");
sleep(2);
printf("子线程退出(资源会自动释放)\n");
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_routine, NULL);
sleep(1); // 让子线程先分离
// 尝试join分离的线程(会失败)
int ret = pthread_join(tid, NULL);
if (ret != 0) {
printf("join失败(线程已分离): %s\n", strerror(ret));
}
return 0;
}
5.3.2 其他线程分离
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* thread_routine(void* arg) {
printf("子线程运行中...\n");
sleep(3);
printf("子线程退出\n");
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_routine, NULL);
// 主线程分离子线程
pthread_detach(tid);
printf("主线程已分离子线程\n");
// 不能join已分离的线程
// pthread_join(tid, NULL); // 这会失败
sleep(4); // 等待子线程完成
return 0;
}
5.4 joinable vs detached对比
┌──────────────┬─────────────────┬─────────────────┐
│ 特性 │ joinable │ detached │
├──────────────┼─────────────────┼─────────────────┤
│ 默认状态 │ 是 │ 否 │
├──────────────┼─────────────────┼─────────────────┤
│ 资源回收 │ 需要join │ 自动释放 │
├──────────────┼─────────────────┼─────────────────┤
│ 可以join │ 是 │ 否 │
├──────────────┼─────────────────┼─────────────────┤
│ 获取退出状态 │ 可以 │ 不可以 │
├──────────────┼─────────────────┼─────────────────┤
│ 使用场景 │ 需要同步或 │ 不关心退出状态 │
│ │ 获取返回值 │ 的后台任务 │
└──────────────┴─────────────────┴─────────────────┘
5.5 资源泄漏演示
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdint.h> // 为了intptr_t
void* thread_routine(void* arg) {
int id = (intptr_t)arg; // 直接传值转换
printf("子线程 %d 退出\n", id);
return NULL;
}
int main() {
pthread_t tid;
int i;
// 创建10个线程,但不join也不detach(故意演示资源泄漏)
// 注意:这里使用 (void*)(intptr_t)i 安全传值,避免局部变量地址问题
for (i = 0; i < 10; i++) {
pthread_create(&tid, NULL, thread_routine, (void*)(intptr_t)i);
sleep(1); // 让线程有时间退出
}
printf("按Ctrl+C退出,观察进程状态\n");
while (1) {
sleep(1);
}
return 0;
}
查看资源泄漏:
# 运行程序
$ ./leak &
[1] 12345
# 查看线程数
$ ps -Lf -p 12345
UID PID PPID LWP C NLWP SZ RSS PSR STIME TTY TIME CMD
user 12345 12340 12345 0 11 1234 5678 0 10:00 pts/0 00:00:00 ./leak
user 12345 12340 12346 0 11 1234 5678 1 10:00 pts/0 00:00:00 ./leak
...
# NLWP=11(1个主线程 + 10个已退出的子线程)
# 虽然子线程已经退出,但因为没有join或detach,资源仍被占用(这就是资源泄漏)
# 解决方法:
# 1. 使用 pthread_join 回收
# 2. 使用 pthread_detach 自动释放
六、本篇总结
📌 核心知识回顾
1. POSIX线程库
- 头文件:
#include <pthread.h>- 编译选项:
-lpthread- 错误处理:返回错误码(不是-1)
2. 线程创建pthread_create
int pthread_create(pthread_t*, attr, func, arg)- 线程函数签名:
void* func(void*)- pthread_t是线程控制块的地址
3. 线程ID
- pthread_t:pthread库维护,进程级唯一
- LWP:内核维护,系统级唯一
- 主线程LWP == PID
4. 线程终止三种方式
- return:从线程函数返回
- pthread_exit:主动退出,可在任何函数中调用
- pthread_cancel:取消其他线程
5. 线程等待pthread_join
- 阻塞等待线程退出
- 回收线程资源
- 获取线程退出状态
- 只能join一次
6. 线程分离pthread_detach
- 线程退出后自动释放资源
- 不能被join
- 适用于不关心退出状态的场景
7. 资源管理
- joinable线程必须被join,否则资源泄漏
- detached线程自动释放资源
- 不能既join又detach
七、实战练习
为了巩固本篇知识,建议完成以下练习:
练习1:多线程计算
// 创建N个线程,每个线程计算一段数组的和
// 最后主线程汇总所有结果
练习2:生产者消费者(简化版)
// 一个线程生产数据,另一个线程消费数据
// 使用全局变量共享数据(先不考虑同步问题)
练习3:线程池雏形
// 创建固定数量的工作线程
// 主线程分配任务给工作线程
// 使用detach管理线程生命周期
八、承上启下
本篇我们掌握了pthread库的核心API,能够创建、终止、等待和分离线程。但还有几个重要问题没有解决:
疑问1: pthread_t到底指向哪里?
- pthread_t是一个地址
- 这个地址在进程地址空间的哪个区域?
疑问2: 线程栈在哪里?
- 主线程的栈在栈区
- 子线程的栈在哪?
疑问3: 线程如何共享进程资源?
- 代码段、数据段、堆是如何共享的?
- 为什么线程之间可以直接访问全局变量?
下一篇预告:
在第三篇中,我们将深入探讨:
✓ pthread_t的本质(线程控制块TCB)
✓ 线程栈的位置(共享区/文件映射区)
✓ 主线程栈vs子线程栈的区别
✓ 进程地址空间的完整布局
✓ 线程如何共享进程资源
✓ 线程栈的大小限制
✓ 线程封装与设计
💬 互动环节
学完本篇,你应该能够:
- 创建线程并传递参数
- 使用三种方式终止线程
- 正确回收线程资源
- 理解joinable和detached的区别
- 避免资源泄漏
如果这些你都掌握了,说明你已经具备了多线程编程的基本能力!
👍 如果本文对你有帮助,请点赞、收藏、分享!
💭 有疑问欢迎评论区讨论,我会及时回复!
更多推荐




所有评论(0)