【Linux系统编程】(四十)线程控制终极指南:从资源共享到实战操控,带你吃透线程全生命周期
前言在 Linux 多线程开发中“线程控制” 是贯穿始终的核心技能 —— 从线程的创建、终止到等待、分离每一步操作都直接影响程序的性能、稳定性和资源利用率。而要熟练掌握线程控制首先必须理清一个关键问题进程和线程究竟哪些资源共享、哪些资源独占这是理解线程控制逻辑的底层基石。很多开发者在编写多线程程序时常会陷入这样的困境明明调用了pthread_create却创建失败线程退出后出现资源泄漏用pthread_join等待线程却始终阻塞甚至因误操作导致整个进程崩溃。这些问题的根源往往是对线程与进程的资源关系理解不深或是对 POSIX 线程库的控制接口使用不当。本文将从 “进程与线程的资源划分” 入手层层递进讲解 Linux 线程的完整控制流程 —— 包括 POSIX 线程库的使用、线程创建、终止、等待、分离等核心操作全程结合实战代码和底层原理用通俗的语言拆解复杂概念让你不仅 “会用” 线程控制接口更能 “懂原理”轻松避开多线程开发的各种坑。下面就让我们正式开始吧一、先搞懂核心Linux 进程与线程的资源共享与独占要理解线程控制的本质首先要明确线程是进程内部的执行流进程是资源分配的基本单位线程是调度的基本单位。这一定位决定了线程与进程的资源关系 —— 线程共享进程的大部分资源同时拥有少量私有资源。1.1 进程与线程的核心定位在 Linux 系统中进程和线程的核心区别可以用一句话概括进程负责 “占有资源”是操作系统分配资源虚拟地址空间、文件描述符、用户 ID 等的最小单位进程之间相互独立资源不共享。线程负责 “执行任务”是 CPU 调度和执行的最小单位线程不能独立占有资源而是共享所属进程的全部资源。举个通俗的例子如果把计算机系统比作一个大型工厂进程就是工厂里的一个个车间 —— 每个车间都有自己的场地、设备、工具对应进程的资源而线程就是车间里的工人 —— 工人共享车间的所有资源同时各自执行不同的任务协同完成车间的整体目标。一个车间至少有一个工人单线程进程也可以有多个工人多线程进程。1.2 线程的私有资源独属于 “工人” 的工具虽然线程共享进程的大部分资源但为了保证线程的独立执行每个线程都拥有自己的 “私有财产”这些资源不会被其他线程共享线程 IDTID线程的唯一标识分为两种 —— 内核级线程 IDLWPLight Weight Process ID和用户级线程 IDpthread_t。内核级 TID 是系统全局唯一的用户级 TID 是进程内唯一的由 POSIX 线程库维护。线程上下文数据包括一组寄存器的值、程序计数器PC、栈指针SP等用于保存线程的执行状态。当线程切换时内核会保存这些上下文切换回来时恢复保证线程能继续执行。线程私有栈每个线程都有自己的栈空间用于存储局部变量、函数调用参数和返回值。主线程的栈在虚拟地址空间的栈区而子线程的栈通常在共享区mmap 区域通过mmap系统调用分配默认大小一般为 8MB且不能动态增长。errno 变量用于存储线程执行系统调用时的错误码。每个线程都有自己的errno副本避免多个线程同时修改导致错误码混乱。信号屏蔽字线程可以设置自己的信号屏蔽字决定哪些信号会被阻塞不影响其他线程的信号处理。调度优先级线程可以拥有自己的调度优先级内核会根据优先级调度线程执行不同线程的优先级可以不同。1.3 线程共享的进程资源车间里的 “公共设施”同一个进程的所有线程共享进程的全部核心资源这是线程高效协作的基础虚拟地址空间包括代码段Text Segment、数据段Data Segment、堆区Heap、共享区mmap 区域等。线程可以直接访问进程的全局变量、静态变量、堆内存无需额外的通信机制。文件描述符表进程打开的所有文件、socket、管道等资源对应的文件描述符会被所有线程共享。一个线程关闭文件描述符会影响其他线程对该文件的访问。信号处理方式进程中设置的信号处理函数如SIG_IGN忽略、SIG_DFL默认处理、自定义处理函数对所有线程有效。线程可以修改信号处理方式但修改后会作用于整个进程。当前工作目录进程的当前工作目录由所有线程共享一个线程调用chdir()修改工作目录其他线程的工作目录也会随之改变。用户 ID 和组 ID进程的所有者用户 IDUID和组 IDGID对所有线程共享线程无法单独修改自己的 UID/GID。其他进程资源包括进程的 PID、进程组 IDPGID、会话 IDSID、打开的共享内存、消息队列、信号量等均为线程共享。1.4 关键问题单进程本质是 “单线程进程”很多开发者会疑惑之前学习的单进程程序和线程有什么关系答案很简单单进程本质上是 “只有一个线程执行流的进程”。这个线程就是主线程main 线程它拥有进程的全部资源独自执行main函数中的代码。当我们创建新的线程后进程就变成了多线程进程多个线程共享进程资源协同执行不同的任务。理解这一点很重要线程控制的所有操作本质上都是在 “进程的资源框架内” 对执行流进行管理不会改变进程的资源分配状态。二、POSIX 线程库Linux 线程控制的 “工具箱”Linux 系统中线程控制的接口主要由POSIX 线程库pthread 库提供这是一套标准的线程操作 API绝大多数函数都以pthread_为前缀。要使用这些函数必须掌握其编译链接方式、错误处理规则这是线程控制的基础。2.1 编译与链接必须链接 pthread 库POSIX 线程库不是 Linux 系统的默认库因此在编译多线程程序时必须通过-lpthread选项明确链接该库否则会出现 “未定义引用” 错误。编译命令示例# 编译单个文件 gcc thread_demo.c -o thread_demo -lpthread # 编译多个文件 gcc thread1.c thread2.c -o thread_app -lpthread2.2 头文件包含 pthread.h所有pthread库的函数声明、数据类型如pthread_t、pthread_attr_t都定义在pthread.h头文件中使用时必须包含#include pthread.h2.3 错误处理与传统系统调用不同传统的 Linux 系统调用如open、read、fork通常返回 - 1 表示失败并设置全局变量errno来指示错误原因。但 pthread 库的函数错误处理方式不同函数执行成功时返回 0。执行失败时不设置全局errno而是直接返回错误码非 0 值。可以使用strerror()函数将错误码转换为人类可读的错误信息。错误处理示例#include stdio.h #include pthread.h #include string.h // 包含strerror函数 int main() { pthread_t tid; // 故意传递错误的参数如NULL线程函数触发创建失败 int ret pthread_create(tid, NULL, NULL, NULL); if (ret ! 0) { // 用strerror将错误码转换为错误信息 fprintf(stderr, 创建线程失败%s\n, strerror(ret)); return 1; } return 0; }运行结果创建线程失败Invalid argument注意pthread 库也提供了线程内的errno副本以兼容依赖errno的代码但对于 pthread 函数的错误建议直接通过返回值判断效率更高。三、线程创建启动一个新的执行流线程创建是线程控制的第一步通过pthread_create函数创建新线程新线程会执行指定的函数与主线程并行运行。3.1 函数原型与参数解析int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);参数详解thread输出参数指向pthread_t类型的变量用于存储新创建线程的用户级 ID进程内唯一。后续对该线程的操作如终止、等待都需要通过这个 ID。attr线程属性设置指定线程的栈大小、调度优先级、分离状态等。若为NULL表示使用默认属性。start_routine函数指针指向线程启动后要执行的函数。该函数的返回值类型为void*参数类型也为void*这是 POSIX 标准规定的线程函数签名便于传递任意类型的数据。arg传递给start_routine函数的参数可以是任意类型的指针。若需要传递多个参数可封装为结构体将结构体指针作为arg传入。返回值成功返回 0。失败返回非 0 错误码常见错误码包括EINVAL参数无效如start_routine为 NULL、EAGAIN系统资源不足无法创建新线程等。3.2 线程创建实战示例示例 1创建简单线程执行无参数函数#include stdio.h #include pthread.h #include unistd.h // 包含sleep函数 // 线程执行函数 void *thread_func(void *arg) { // 循环打印线程信息 for (int i 0; i 5; i) { printf(子线程我正在运行线程ID %lu\n, (unsigned long)pthread_self()); sleep(1); // 休眠1秒 } return NULL; // 线程退出返回NULL } int main() { pthread_t tid; // 创建线程 int ret pthread_create(tid, NULL, thread_func, NULL); if (ret ! 0) { fprintf(stderr, 创建线程失败%s\n, strerror(ret)); return 1; } printf(主线程成功创建子线程子线程ID %lu\n, (unsigned long)tid); // 主线程循环打印信息 for (int i 0; i 5; i) { printf(主线程我正在运行线程ID %lu\n, (unsigned long)pthread_self()); sleep(1); } // 等待子线程结束后续详细讲解 pthread_join(tid, NULL); printf(主线程子线程已退出程序结束\n); return 0; }编译运行gcc thread_simple.c -o thread_simple -lpthread ./thread_simple运行结果主线程和子线程交替执行主线程成功创建子线程子线程ID 140703347508992 主线程我正在运行线程ID 140703355896640 子线程我正在运行线程ID 140703347508992 主线程我正在运行线程ID 140703355896640 子线程我正在运行线程ID 140703347508992 主线程我正在运行线程ID 140703355896640 子线程我正在运行线程ID 140703347508992 主线程我正在运行线程ID 140703355896640 子线程我正在运行线程ID 140703347508992 主线程我正在运行线程ID 140703355896640 子线程我正在运行线程ID 140703347508992 主线程子线程已退出程序结束示例 2传递参数给线程函数单个参数#include stdio.h #include pthread.h #include unistd.h // 线程执行函数接收一个整数参数 void *thread_with_arg(void *arg) { // 将void*类型转换为int类型 int num *(int*)arg; for (int i 0; i num; i) { printf(子线程第%d次运行线程ID %lu\n, i1, (unsigned long)pthread_self()); sleep(1); } return NULL; } int main() { pthread_t tid; int run_count 3; // 要传递给线程的参数 // 创建线程传递run_count的地址 int ret pthread_create(tid, NULL, thread_with_arg, run_count); if (ret ! 0) { fprintf(stderr, 创建线程失败%s\n, strerror(ret)); return 1; } // 等待子线程结束 pthread_join(tid, NULL); printf(主线程子线程执行完毕程序结束\n); return 0; }示例 3传递多个参数给线程函数结构体封装#include stdio.h #include pthread.h #include unistd.h #include string.h // 定义要传递给线程的参数结构体 typedef struct { char name[20]; // 线程名称 int age; // 自定义整数参数 float score; // 自定义浮点数参数 } ThreadArgs; // 线程执行函数接收结构体参数 void *thread_with_struct_arg(void *arg) { ThreadArgs *args (ThreadArgs*)arg; printf(子线程线程名称 %s年龄 %d分数 %.2f\n, args-name, args-age, args-score); // 模拟业务逻辑 sleep(2); printf(子线程执行完毕\n); return NULL; } int main() { pthread_t tid; // 初始化参数结构体 ThreadArgs args {Worker-1, 25, 98.5}; // 创建线程传递结构体指针 int ret pthread_create(tid, NULL, thread_with_struct_arg, args); if (ret ! 0) { fprintf(stderr, 创建线程失败%s\n, strerror(ret)); return 1; } // 等待子线程结束 pthread_join(tid, NULL); printf(主线程程序结束\n); return 0; }3.3 线程 ID 的两种类型与获取方式在 Linux 中线程有两种 ID用途不同必须区分清楚用户级线程 IDpthread_t由 POSIX 线程库维护是进程内唯一的标识仅在当前进程中有效。通过pthread_create的thread参数获取或通过pthread_self()函数获取当前线程的 ID。类型pthread_t的具体实现可能是整数、指针等不建议直接用%d打印推荐用%lu转换为unsigned long。内核级线程 IDLWPLight Weight Process ID由 Linux 内核维护是系统全局唯一的标识内核调度线程时使用。可以通过ps -aL命令查看LWP 列显示的就是内核级线程 ID。主线程的 LWP 与进程 IDPID相同。查看线程信息的 bash 命令# 编译运行线程程序后查看线程信息 ps -aL | grep 程序名示例运行示例 1 的thread_simple程序后执行命令ps -aL | grep thread_simple输出结果12345 12345 pts/0 00:00:00 thread_simple # 主线程PID12345LWP12345 12345 12346 pts/0 00:00:00 thread_simple # 子线程PID12345LWP123463.4 线程创建的注意事项线程函数的返回值线程函数的返回值类型为void*可以返回任意类型的数据但返回的内存必须是全局变量或堆内存malloc分配不能是局部变量线程退出后局部变量会被释放导致野指针。参数传递的安全性传递给线程的参数如果是局部变量要确保线程执行期间该变量不被销毁。如果主线程在子线程启动前就退出局部变量会被释放子线程访问时会出现未定义行为。线程创建后的执行顺序线程创建后主线程和子线程的执行顺序由 CPU 调度器决定无法预测。不要假设子线程会在主线程之前执行或反之。系统线程数量限制Linux 系统对单个进程的线程数量有默认限制通常为 1024超出限制会导致pthread_create返回EAGAIN错误。可以通过修改/etc/security/limits.conf文件调整限制。四、线程终止优雅结束线程执行线程终止是指线程停止执行释放自己的私有资源如栈、寄存器上下文等但进程的共享资源不会被释放。Linux 提供了三种合法的线程终止方式以及一种不推荐的强制终止方式。4.1 线程终止的三种合法方式方式 1从线程函数 return 返回推荐线程函数执行完毕后通过return语句返回线程正常终止。这种方式最优雅可以自然地清理线程内的局部变量并且可以返回线程的执行结果。示例#include stdio.h #include pthread.h #include stdlib.h // 线程函数返回一个整数结果 void *thread_return(void *arg) { int *result (int*)malloc(sizeof(int)); *result 100; // 线程执行结果 printf(子线程执行完毕返回结果 %d\n, *result); return (void*)result; // 返回堆内存的指针 } int main() { pthread_t tid; pthread_create(tid, NULL, thread_return, NULL); // 等待线程结束获取返回值 void *ret; pthread_join(tid, ret); printf(主线程获取子线程返回值 %d\n, *(int*)ret); free(ret); // 释放子线程分配的堆内存 return 0; }方式 2调用 pthread_exit 函数终止自己线程可以通过调用pthread_exit函数主动终止自己该函数不会返回相当于线程的 “自杀” 函数。pthread_exit的参数可以传递线程的退出状态供其他线程通过pthread_join获取。函数原型void pthread_exit(void *value_ptr);参数value_ptr指向线程退出状态的指针与线程函数return的返回值作用相同。该指针指向的内存必须是全局变量或堆内存不能是局部变量。返回值无返回值线程调用后直接终止。示例#include stdio.h #include pthread.h #include stdlib.h void *thread_exit_demo(void *arg) { 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; pthread_create(tid, NULL, thread_exit_demo, NULL); void *ret; pthread_join(tid, ret); printf(主线程获取子线程退出状态 %d\n, *(int*)ret); free(ret); return 0; }方式 3主线程正常退出main函数 return主线程执行完main函数的代码后return相当于调用exit函数会终止整个进程进程内的所有线程都会随之退出。示例#include stdio.h #include pthread.h #include unistd.h void *thread_func(void *arg) { while (1) { printf(子线程正在运行...\n); sleep(1); } return NULL; } int main() { pthread_t tid; pthread_create(tid, NULL, thread_func, NULL); // 主线程休眠3秒后退出 sleep(3); printf(主线程退出所有子线程将被终止\n); return 0; // 主线程退出子线程也会随之终止 }4.2 线程的强制终止调用 pthread_cancel 函数一个线程可以调用pthread_cancel函数强制终止同一进程中的另一个线程。这种方式相当于 “杀死” 其他线程使用时需要谨慎因为可能导致资源泄漏如线程持有互斥锁未释放。函数原型int pthread_cancel(pthread_t thread);参数thread要终止的线程的用户级 IDpthread_t类型。返回值成功返回 0失败返回非 0 错误码。注意事项pthread_cancel只是向目标线程发送一个 “取消请求”目标线程不会立即终止而是在到达 “取消点” 时才会响应请求。取消点是线程执行过程中检查是否有取消请求的位置常见的取消点包括sleep、read、write、pthread_join等系统调用和库函数。线程可以通过pthread_setcancelstate函数设置自己的取消状态启用或禁用通过pthread_setcanceltype函数设置取消类型立即取消或延迟取消。示例#include stdio.h #include pthread.h #include unistd.h void *thread_to_cancel(void *arg) { int i 0; while (1) { printf(子线程第%d次运行等待取消...\n, i); sleep(1); // sleep是取消点 } return NULL; } int main() { pthread_t tid; pthread_create(tid, NULL, thread_to_cancel, NULL); // 主线程休眠3秒后强制终止子线程 sleep(3); printf(主线程发送取消请求\n); int ret pthread_cancel(tid); if (ret ! 0) { fprintf(stderr, 取消线程失败%s\n, strerror(ret)); return 1; } // 等待子线程终止获取取消状态 void *cancel_ret; pthread_join(tid, cancel_ret); if (cancel_ret PTHREAD_CANCELED) { printf(主线程子线程已被成功取消\n); } return 0; }运行结果子线程第1次运行等待取消... 子线程第2次运行等待取消... 子线程第3次运行等待取消... 主线程发送取消请求 主线程子线程已被成功取消4.3 线程终止的禁忌避免使用 exit 函数线程中绝对不要调用exit函数exit函数的作用是终止整个进程而不是当前线程。一个线程调用exit会导致进程内的所有线程包括主线程都被终止这通常不是我们想要的结果。错误示例#include stdio.h #include pthread.h #include unistd.h #include stdlib.h // 包含exit函数 void *bad_thread(void *arg) { printf(子线程调用exit终止\n); exit(EXIT_FAILURE); // 错误终止整个进程主线程也会被终止 return NULL; } int main() { pthread_t tid; pthread_create(tid, NULL, bad_thread, NULL); sleep(1); printf(主线程这行代码永远不会被执行\n); // 不会输出 return 0; }五、线程等待等待线程终止回收资源线程终止后其私有资源如栈、线程控制块 TCB不会自动释放仍然占用进程的地址空间。如果主线程不处理这些资源会导致系统资源泄漏。线程等待就是主线程或其他线程等待目标线程终止回收其资源并获取其退出状态的过程。5.1 为什么需要线程等待回收资源线程终止后其 TCB、栈等资源需要被回收否则会一直占用内存长期运行可能导致内存耗尽。获取退出状态主线程可以通过等待获取子线程的退出状态如返回值、是否被取消判断子线程是否正常执行完毕。同步线程执行主线程可以通过等待确保子线程执行完毕后再继续执行后续代码避免出现 “子线程未完成任务主线程已退出” 的情况。5.2 线程等待函数pthread_joinPOSIX 线程库提供pthread_join函数实现线程等待该函数会阻塞调用线程通常是主线程直到目标线程终止。函数原型int pthread_join(pthread_t thread, void **value_ptr);参数详解thread要等待的目标线程的用户级 IDpthread_t类型。value_ptr二级指针用于存储目标线程的退出状态。如果不关心退出状态可以传递NULL。返回值成功返回 0失败返回非 0 错误码如ESRCH目标线程不存在EDEADLK死锁如线程等待自己。不同终止方式对应的退出状态线程终止方式value_ptr存储的内容从线程函数return返回线程函数的返回值void*类型调用pthread_exit终止pthread_exit的参数void*类型被pthread_cancel取消特殊常量PTHREAD_CANCELED线程异常终止如段错误无定义进程会随之崩溃无法通过pthread_join获取5.3 线程等待实战示例示例 1等待 return 返回的线程获取返回值#include stdio.h #include pthread.h #include stdlib.h void *thread_return_val(void *arg) { int *ret (int*)malloc(sizeof(int)); *ret 300; printf(子线程return返回结果 %d\n, *ret); return (void*)ret; } int main() { pthread_t tid; pthread_create(tid, NULL, thread_return_val, NULL); // 等待线程获取返回值 void *value_ptr; int ret pthread_join(tid, value_ptr); if (ret ! 0) { fprintf(stderr, 等待线程失败%s\n, strerror(ret)); return 1; } printf(主线程子线程返回值 %d\n, *(int*)value_ptr); free(value_ptr); // 释放子线程分配的内存 return 0; }示例 2等待 pthread_exit 终止的线程获取退出状态#include stdio.h #include pthread.h #include stdlib.h void *thread_exit_val(void *arg) { char *msg (char*)malloc(20); strcpy(msg, 线程执行成功); printf(子线程pthread_exit退出\n); pthread_exit((void*)msg); } int main() { pthread_t tid; pthread_create(tid, NULL, thread_exit_val, NULL); void *value_ptr; pthread_join(tid, value_ptr); printf(主线程子线程退出状态 %s\n, (char*)value_ptr); free(value_ptr); return 0; }示例 3等待被取消的线程判断取消状态#include stdio.h #include pthread.h #include unistd.h void *thread_canceled(void *arg) { while (1) { printf(子线程运行中...\n); sleep(1); // 取消点 } return NULL; } int main() { pthread_t tid; pthread_create(tid, NULL, thread_canceled, NULL); sleep(2); pthread_cancel(tid); // 发送取消请求 void *value_ptr; pthread_join(tid, value_ptr); if (value_ptr PTHREAD_CANCELED) { printf(主线程子线程已被取消\n); } else { printf(主线程子线程正常退出\n); } return 0; }示例 4不关心退出状态仅回收资源#include stdio.h #include pthread.h #include unistd.h void *thread_no_val(void *arg) { printf(子线程执行任务...\n); sleep(2); return NULL; } int main() { pthread_t tid; pthread_create(tid, NULL, thread_no_val, NULL); // 传递NULL给value_ptr不关心退出状态 pthread_join(tid, NULL); printf(主线程子线程已终止资源已回收\n); return 0; }5.4 线程等待的注意事项只能等待 joinable 状态的线程默认情况下线程是joinable状态必须通过pthread_join等待否则会资源泄漏。如果线程设置为分离状态detachedpthread_join会返回EINVAL错误。避免死锁线程不能等待自己会导致死锁也不能重复等待同一个线程第二次等待会返回ESRCH错误。等待的阻塞特性pthread_join是阻塞函数调用后会暂停当前线程的执行直到目标线程终止。如果需要非阻塞等待可以结合pthread_tryjoin_np函数非标准函数_np表示 non-portable。六、线程分离自动回收资源无需等待默认情况下线程是joinable状态需要通过pthread_join等待回收资源。但在某些场景下如不关心线程的退出状态pthread_join会成为一种负担。这时可以将线程设置为分离状态detached线程终止后会自动释放资源无需其他线程等待。6.1 线程分离的作用与原理作用线程设置为分离状态后终止时会自动回收其 TCB、栈等资源不需要其他线程调用pthread_join避免资源泄漏。原理分离状态的线程其joinid字段会指向自身pd-joinid pd内核会在线程终止时自动清理其资源无需等待其他线程的pthread_join调用。6.2 线程分离函数pthread_detach通过pthread_detach函数可以将线程设置为分离状态该函数可以由其他线程调用也可以由线程自身调用。函数原型int pthread_detach(pthread_t thread);参数thread要设置为分离状态的线程的用户级 ID。返回值成功返回 0失败返回非 0 错误码。6.3 线程分离的两种方式方式 1线程自身调用 pthread_detach推荐线程在启动后主动调用pthread_detach(pthread_self())将自己设置为分离状态。这种方式最灵活线程可以自主决定是否分离。示例#include stdio.h #include pthread.h #include unistd.h void *detached_thread_self(void *arg) { // 将自己设置为分离状态 int ret pthread_detach(pthread_self()); if (ret ! 0) { fprintf(stderr, 设置线程分离失败%s\n, strerror(ret)); return NULL; } printf(分离线程我是分离状态终止后会自动回收资源\n); sleep(2); printf(分离线程执行完毕即将终止\n); return NULL; } int main() { pthread_t tid; pthread_create(tid, NULL, detached_thread_self, NULL); // 主线程休眠3秒确保子线程执行完毕 sleep(3); printf(主线程程序结束无需调用pthread_join\n); return 0; }方式 2其他线程调用 pthread_detach主线程或其他线程在创建目标线程后调用pthread_detach将其设置为分离状态。这种方式需要确保在目标线程终止前完成分离设置否则可能导致资源泄漏。示例#include stdio.h #include pthread.h #include unistd.h void *detached_thread_other(void *arg) { printf(分离线程由主线程设置为分离状态\n); sleep(2); printf(分离线程执行完毕\n); return NULL; } int main() { pthread_t tid; pthread_create(tid, NULL, detached_thread_other, NULL); // 主线程将子线程设置为分离状态 int ret pthread_detach(tid); if (ret ! 0) { fprintf(stderr, 设置线程分离失败%s\n, strerror(ret)); return 1; } sleep(3); printf(主线程程序结束\n); return 0; }6.4 线程分离的注意事项joinable 与 detached 状态互斥一个线程不能同时是joinable和detached状态。一旦设置为detached状态就不能再通过pthread_join等待否则会返回EINVAL错误。设置分离状态的时机必须在线程终止前设置分离状态否则线程终止后资源无法回收导致泄漏。无法获取退出状态分离状态的线程终止后其退出状态会被自动销毁无法通过任何方式获取。如果需要获取线程的退出状态不能使用分离状态。主线程退出的影响即使线程是分离状态若主线程提前退出调用exit或main函数return所有子线程都会被终止分离状态仅影响线程终止后的资源回收不影响线程的生命周期。6.5 错误示例等待分离状态的线程#include stdio.h #include pthread.h #include unistd.h #include string.h void *detached_thread_err(void *arg) { pthread_detach(pthread_self()); // 设置为分离状态 sleep(2); return NULL; } int main() { pthread_t tid; pthread_create(tid, NULL, detached_thread_err, NULL); sleep(1); // 确保子线程已设置为分离状态 // 错误等待分离状态的线程 int ret pthread_join(tid, NULL); if (ret ! 0) { fprintf(stderr, 等待分离线程失败%s\n, strerror(ret)); return 1; } return 0; }运行结果等待分离线程失败Invalid argument总结线程控制是 Linux 多线程开发的基础掌握这些操作后才能进一步学习线程同步互斥锁、条件变量、信号量、线程安全等高级主题。后续将继续讲解线程同步、线程安全、线程池等高级主题敬请关注创作不易若本文对你有帮助欢迎点赞、收藏、关注

相关新闻

按功能拆分 Pinia 仓库

按功能拆分 Pinia 仓库

1、示例仓库结构:(以下示例来自AI,存档是为了后期看到此分割结构)src/ ├── stores/ # 所有 Pinia 仓库的根目录 │ ├── index.js # 仓库的统一导出入口(可选,但推荐) │ …

2026/5/17 6:50:53 阅读更多 →
云电脑测试版

云电脑测试版

链接:https://pan.quark.cn/s/f7b0c8ebf826最近在使用VMware虚拟系统安装不同版本的系统来测试程序,发现启动慢还占用系统资源出现卡顿问题,很影响进度,于是搭建了云电脑非常实用,要使用的时间直接远程登录直接秒开还能减轻系统负担,完美!云电脑已经续费半年,7*24不…

2026/7/4 20:08:10 阅读更多 →
一招教你正确打开星绘友晴天steam版

一招教你正确打开星绘友晴天steam版

网易雷火工作室研发的宇宙生活模拟多人派对融合型休闲游戏《星绘友晴天》定于2月24日steam新品节开放试玩Demo,2月28日开启iOS/安卓/PC三端同步不限量不删档测试作为网易又一治愈种田和多人派对玩法的游戏,早在初期就已收获测试玩家的一众好评游戏首创“…

2026/5/17 5:19:39 阅读更多 →

最新新闻

会议主题:{{主题}}

会议主题:{{主题}}

会议主题:{{主题}} 【免费下载链接】logseq A privacy-first, open-source platform for knowledge management and collaboration. Download link: http://github.com/logseq/logseq/releases. roadmap: https://logseq.io/p/NX4mc_ggEV 项目地址: https://gitco…

2026/7/6 15:30:26 阅读更多 →
三步搞定跨平台端口转发:bore TCP隧道工具终极指南 [特殊字符]

三步搞定跨平台端口转发:bore TCP隧道工具终极指南 [特殊字符]

三步搞定跨平台端口转发:bore TCP隧道工具终极指南 🚀 【免费下载链接】bore 🕳 bore is a simple CLI tool for making tunnels to localhost 项目地址: https://gitcode.com/gh_mirrors/bo/bore 在现代开发中,你是否经常…

2026/7/6 15:28:25 阅读更多 →
Notepad--:跨平台文本编辑器的三大核心优势与实战应用

Notepad--:跨平台文本编辑器的三大核心优势与实战应用

Notepad--:跨平台文本编辑器的三大核心优势与实战应用 【免费下载链接】notepad-- 一个支持windows/linux/mac的文本编辑器,目标是做中国人自己的编辑器,来自中国。 项目地址: https://gitcode.com/GitHub_Trending/no/notepad-- 在当…

2026/7/6 15:28:25 阅读更多 →
深度解析:Stability AI生成模型部署的5大实战技巧与问题解决

深度解析:Stability AI生成模型部署的5大实战技巧与问题解决

深度解析:Stability AI生成模型部署的5大实战技巧与问题解决 【免费下载链接】generative-models Generative Models by Stability AI 项目地址: https://gitcode.com/GitHub_Trending/ge/generative-models Stability AI的生成模型套件为开发者和研究人员提…

2026/7/6 15:26:23 阅读更多 →
PyTorch 2.0 深度可分离卷积实战:MobileNet 模块参数量减少 8-9 倍

PyTorch 2.0 深度可分离卷积实战:MobileNet 模块参数量减少 8-9 倍

PyTorch 2.0深度可分离卷积实战:MobileNet模块参数量优化策略 当我们在移动设备上部署深度学习模型时,模型大小和计算效率往往成为关键瓶颈。传统卷积神经网络在参数数量和计算量上的高需求,使得它们在资源受限的环境中难以高效运行。深度可分…

2026/7/6 15:24:23 阅读更多 →
HOScrcpy:鸿蒙开发者必备的远程真机投屏终极解决方案

HOScrcpy:鸿蒙开发者必备的远程真机投屏终极解决方案

HOScrcpy:鸿蒙开发者必备的远程真机投屏终极解决方案 【免费下载链接】鸿蒙远程真机工具 该工具主要提供鸿蒙系统下基于视频流的投屏功能,帧率基本持平真机帧率,达到远程真机的效果。 项目地址: https://gitcode.com/OpenHarmonyToolkitsPl…

2026/7/6 15:24:23 阅读更多 →

日新闻

H2 与 MySQL 单元测试兼容性:5 个关键 SQL 语句差异与规避方案

H2 与 MySQL 单元测试兼容性:5 个关键 SQL 语句差异与规避方案

H2与MySQL单元测试兼容性:5个关键SQL语句差异与规避方案1. 单元测试中的数据库兼容性挑战在Java开发领域,单元测试是保证代码质量的重要环节。当应用涉及数据库操作时,测试环境的搭建往往成为开发者的痛点。H2数据库因其轻量级、内存模式和快…

2026/7/6 0:01:17 阅读更多 →
Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘

Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘

Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘 【免费下载链接】rbtray A fork of RBTray from http://sourceforge.net/p/rbtray/code/. 项目地址: https://gitcode.com/gh_mirrors/rb/rbtray 你是否厌倦了Windows任务栏上密密麻麻的图标&…

2026/7/6 0:01:17 阅读更多 →
Visual C++ 运行时库一键安装终极指南:告别DLL缺失烦恼

Visual C++ 运行时库一键安装终极指南:告别DLL缺失烦恼

Visual C 运行时库一键安装终极指南:告别DLL缺失烦恼 【免费下载链接】vcredist AIO Repack for latest Microsoft Visual C Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist 你是否曾经遇到过这样的情况:下载了…

2026/7/6 0:05:19 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/6 8:11:50 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/6 8:11:52 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/6 6:52:56 阅读更多 →

月新闻