Linux 学习笔记(三十二):线程控制——创建、等待与分离

一、 线程的终止

线程有三种终止方式:

  1. return:函数返回。注意,主线程返回会导致整个进程退出。

  2. pthread_exit :谁调用谁退出,不影响其他线程。可以返回一个 void* 类型的指针给等待者。

  3. 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 本质上就是进程地址空间(共享区)里的一个地址。

相关推荐
女神下凡1 分钟前
芯参谋(107):杰理AC_中科蓝讯_开发指南
linux·服务器·网络·单片机·嵌入式硬件
落羽的落羽8 分钟前
【C++】C++17的主要特性
linux·服务器·开发语言·c++·算法·机器学习
岑梓铭1 小时前
《考研408数据结构》第七章(B树 和 B+树)复习笔记
数据结构·笔记·b树·考研·408
SQL771 小时前
IT运维发展史,一部宏伟史诗
运维·运维发展史
乌暮2 小时前
Spring AOP 与 Spring 事务:用大白话彻底理解原理与实践
java·数据库·spring boot·后端·sql·学习·spring
分布式存储与RustFS3 小时前
Terraform 状态文件老在本地丢、并发还撞锁:把 state 锁进 RustFS
运维·云原生·开源·对象存储·分布式存储·s3·性能基准
csdn_aspnet3 小时前
macOS 10 、Docker 玩轉 MySQL
运维·mysql·macos·docker·容器·mac
爱和冰阔落5 小时前
【Linux网络】TCP 半包粘包怎么解决?从长度报头、JSON 拆包到网络计算器完整实战
linux·运维·网络·tcp/ip
Go Work5 小时前
docker 占满根分区时先分清谁在吃盘
经验分享·笔记
3095566666 小时前
股票接口返回 404 与空数组的排查笔记:证书状态与级别参数
java·笔记·python·ios·iphone·股票数据api·股票数据接口