一、 线程的终止
线程有三种终止方式:
-
return:函数返回。注意,主线程返回会导致整个进程退出。
-
pthread_exit:谁调用谁退出,不影响其他线程。可以返回一个void*类型的指针给等待者。 -
pthread_cancel:在另一个线程中调用,用于强制取消目标线程。
二、 线程的等待(pthread_join)
与进程的 wait 类似,线程退出后必须由其他线程调用 pthread_join 来回收资源,否则会造成内存泄漏。
-
pthread_join会阻塞调用它的线程,直到目标线程结束。 -
通过二级指针接收目标线程的返回值。

三、 线程的分离(pthread_detach)
如果主线程不需要关心子线程的结果,可以使用 pthread_detach 将线程分离。
-
被分离的线程运行结束后,操作系统会自动回收它的资源。
-
分离的线程不能被
pthread_join,强行 join 会报错。
四、 深度印证:线程 ID 的底层本质
(插入实战 4 的地址输出截图)
通过打印 pthread_self() 的返回值以及线程内部局部变量的地址,我们发现:
pthread_t 类型的线程 ID 本质上就是一个虚拟内存地址。它指向了进程共享区(mmap区域)中的一块内存(TCB),这块内存管理着该线程的私有栈和寄存器信息。
总结
熟练掌握线程的创建、退出、等待和分离,是多线程编程的第一步。牢记:用完线程一定要 join 或 detach,否则资源必将泄漏。
自我实战操作
咱们来写一段代码,把创建、等待、终止、分离全跑一遍。
bash
cd ~/linux_study/thread_test
实战 1:线程的创建、带参数与返回值
cpp
vim thread_return.c
(输入以下代码)
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
// 线程执行的函数
void *thread_run(void *arg) {
char *msg = (char *)arg;
printf("子线程收到参数: %s\n", msg);
// 动态分配返回值内存(不能在栈上分配,因为函数结束栈就销毁了)
int *ret_val = (int *)malloc(sizeof(int));
*ret_val = 666;
printf("子线程准备退出...\n");
pthread_exit((void *)ret_val); // 带返回值退出
}
int main() {
pthread_t tid;
char *msg = "Hello Thread!";
// 1. 创建线程
pthread_create(&tid, NULL, thread_run, (void *)msg);
// 2. 等待线程结束,并获取返回值
void *ret = NULL;
pthread_join(tid, &ret);
printf("主线程收到子线程的返回值: %d\n", *(int *)ret);
free(ret); // 释放子线程 malloc 的内存
return 0;
}
编译运行:
bash
gcc thread_return.c -o thread_return -lpthread
./thread_return
实战 2:暗杀线程(pthread_cancel)
bash
vim thread_cancel.c
(输入以下代码)
cpp
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void *thread_run(void *arg) {
while(1) {
printf("我是子线程,我在疯狂输出...\n");
sleep(1);
}
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_run, NULL);
sleep(3); // 让子线程先跑3秒
printf("主线程受不了了,准备干掉子线程!\n");
pthread_cancel(tid); // 发送取消请求
void *ret;
pthread_join(tid, &ret);
if (ret == PTHREAD_CANCELED) {
printf("子线程被成功取消!\n");
}
return 0;
}
(编译运行 gcc thread_cancel.c -o thread_cancel -lpthread && ./thread_cancel)

👉 现象 :子线程输出 3 秒后,被主线程无情暗杀,pthread_join 返回的 ret 会变成宏 PTHREAD_CANCELED。
实战 3:体验线程分离(pthread_detach)
bash
vim thread_detach.c
(输入以下代码)
cpp
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void *thread_run(void *arg) {
printf("子线程正在运行,马上要退出了...\n");
sleep(1);
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_run, NULL);
// 将子线程分离
pthread_detach(tid);
// 此时尝试 join 会直接报错(返回非0值)
int ret = pthread_join(tid, NULL);
if (ret != 0) {
printf("主线程尝试 join 失败!错误码: %d (%s)\n", ret, strerror(ret));
}
printf("主线程自己睡觉去了,不管子线程了...\n");
sleep(2);
return 0;
}

👉 现象 :pthread_join 报错,说明分离后的线程无法被等待,系统会自动回收它。
实战 4:观察线程地址空间(印证文档)
bash
vim thread_addr.c
(输入以下代码)
cpp
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void *thread_run(void *arg) {
int local_var = 10; // 局部变量
printf("子线程:pthread_self() = %lu, 局部变量地址 = %p\n", pthread_self(), &local_var);
sleep(2);
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_run, NULL);
sleep(1);
int main_local = 20;
printf("主线程:pthread_self() = %lu, 局部变量地址 = %p\n", pthread_self(), &main_local);
pthread_join(tid, NULL);
return 0;
}
(编译运行 gcc thread_addr.c -o thread_addr -lpthread && ./thread_addr)

👉 现象 :你会发现 pthread_self() 打印出来的值和子线程的局部变量地址(&local_var)非常接近。这证明了 pthread_t 本质上就是进程地址空间(共享区)里的一个地址。
