线程进阶实战:资源划分与线程控制核心指南
个人主页Cx330❄️个人专栏《C语言》《LeetCode刷题集》《数据结构-初阶》《C知识分享》《优选算法指南-必刷经典100题》《Linux操作系统》:从入门到入魔《Git深度解析》:版本管理实战全解心向往之行必能至Cx330的简介目录前言一、前置认知为什么需要资源划分与线程控制二、 进程 VS 线程资源共享与私有划分1.1 线程间共享的进程资源1.2 线程私有的独立资源1.3 关键问题为什么一个线程崩溃整个进程都会退出三、线程控制——精准管控实现高效协作3.1 pthread 线程库3.2 线程创建pthread_create3.3 多线程创建 – 构建任务3.4 线程终止3 种合法终止方式3.4.1 方式 1线程入口函数 return 返回3.4.2 方式 2pthread_exit线程主动终止自身3.4.3 方式 3pthread_cancel终止同进程的其他线程3.5 线程等待pthread_join3.6 多线程的优化写法——线程等待3.7 线程分离pthread_detach四、总结线程进阶的核心逻辑前言在多线程开发中入门级的线程创建与启动仅仅是起点。当业务场景变得复杂并发量提升、资源竞争加剧时如何合理划分资源、精准控制线程行为直接决定了程序的性能、稳定性与可维护性。本文聚焦线程进阶的两大核心模块——资源划分与线程控制结合底层原理与实战场景帮你跳出“只会用线程不会管线程”的困境真正实现多线程的高效运用。一、前置认知为什么需要资源划分与线程控制操作系统中进程是资源分配的基本单位而线程是CPU调度的基本单位线程依赖进程存在并共享其大部分资源。在未进行合理资源划分与线程控制的场景中极易出现两大问题资源竞争混乱多个线程争抢同一资源如内存、文件描述符引发竞态条件、死锁等问题导致程序运行异常或性能瓶颈线程行为失控线程创建/销毁频繁、执行顺序不可控、空闲线程占用资源不仅会增加CPU上下文切换开销还可能导致系统资源耗尽甚至JVM崩溃。简单来说资源划分是“合理分配蛋糕”让每个线程都能高效获取所需资源线程控制是“规范吃蛋糕的秩序”让线程按预期执行、协作避免混乱。二者相辅相成是多线程进阶的核心必修课。二、 进程 VS 线程资源共享与私有划分线程的 “轻量化 ”本质是共享了进程的绝大部分资源仅私有运行必需的最小上下文。我们先把共享与私有资源划清这是理解线程所有特性的基础。1.1 线程间共享的进程资源同进程内的所有线程共享进程地址空间内的绝大部分资源无需额外申请这也是线程创建 / 切换成本极低的核心原因地址空间核心段代码段Text Segment、数据段Data Segment、堆区、共享区全局变量、静态变量、堆内申请的内存所有线程都可直接访问修改文件系统相关文件描述符表、当前工作目录、用户 id / 组 id一个线程打开的文件其他线程可直接读写信号相关每种信号的处理方式SIG_IGN/SIG_DFL/ 自定义处理函数信号是发给进程的任意线程收到信号都会触发整个进程的信号处理函数进程内核数据页表、mm_struct 内存描述符、vm_area_struct 内存区域结构1.2 线程私有的独立资源线程要独立被 CPU 调度执行必须私有运行时的上下文数据这些资源线程间完全隔离互不影响线程 IDTID用户态 pthread 库的唯一标识内核态对应 LWP 号寄存器上下文CPU 寄存器的值线程切换时保存 / 恢复保证执行流不混乱独立栈空间每个线程有自己的私有栈存放局部变量、函数调用栈帧互不干扰errno 变量系统调用错误码线程私有避免多线程间错误码互相覆盖信号屏蔽字每个线程可独立设置信号屏蔽规则不影响其他线程调度优先级线程可独立设置调度优先级由内核单独调度线程局部存储TLS线程私有的全局变量仅当前线程可访问1.3 关键问题为什么一个线程崩溃整个进程都会退出这是面试高频考点结合资源划分就能瞬间理解线程触发异常除零、野指针、非法内存访问内核会向进程发送致命信号而非单独发给线程信号的处理方式是进程级共享的进程收到致命信号后会直接终止整个进程进程终止后地址空间、文件描述符等所有资源都会被回收所有线程自然随之退出。三、线程控制——精准管控实现高效协作Linux 内核仅提供轻量级进程的创建能力我们日常使用的线程接口都来自POSIX 标准的 pthread 线程库所有接口都以pthread_开头编译时必须通过-lpthread链接线程库3.1 pthread 线程库头文件必须包含pthread.h编译命令gcc xxx.c -o xxx -lpthread核心特点线程库运行在用户态负责线程的管理TCB 线程控制块、线程栈分配底层通过clone系统调用让内核创建 LWP其实我们之前学习的fork也是这样的。代码演示#include iostream #include cstdio #include string #include pthread.h #include unistd.h void *threadrun(void *args) { std::string namestatic_castconst char *(args); while(true) { printf(我是一个新线程:tid: %lu,pid: %d\n,pthread_self(),getpid()); sleep(1); } } int main() { pthread_t tid; pthread_create(tid,nullptr,threadrun,(void*)thread-1); while(true) { printf(创建新线程成功,new tid: %lu,main tid: %lu,pid: %d\n,tid,pthread_self(),getpid()); sleep(1); } }3.2 线程创建pthread_create用于创建一个新的用户态线程是线程控制最基础的接口。函数原型#include pthread.h int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);参数说明参数作用说明thread输出型参数用于返回新创建线程的用户态线程 IDattr线程属性设置传NULL表示使用默认属性栈大小、调度策略等start_routine函数指针线程的入口执行函数线程创建成功后会从这个函数开始执行arg传递给线程入口函数的参数返回值成功返回 0失败直接返回错误码不会设置 errno无需通过 perror 打印用strerror(ret)解析错误信息配套函数pthread_selfpthread_t pthread_self(void);功能获取当前线程的用户态线程 ID和进程的getpid()作用一致。完整实战代码创建线程// ./createThread num int main(int argc, char *argv[]) { if (argc ! 2) { std::cout argv[0] num std::endl; return 1; } int num std::stoi(argv[1]); // 将字符串转化为数字 std::vectorpthread_t tids; for (int i 0; i num; i) { // 如果我们要创建多线程呢 pthread_t tid; pthread_create(tid, nullptr, threadrun, (void *)thread-1); tids.push_back(tid); } sleep(1); for(auto tid:tids) { printf(创建新线程成功,new tid: 0x%lx,main tid: %lu,pid: %d\n, tid, pthread_self(), getpid()); } // 主线程 while (true) { std::coutmain thread running...std::endl; sleep(1); } }关键说明线程的执行顺序由内核调度决定主线程和新线程谁先执行不固定用户态的pthread_t线程 ID本质是进程地址空间中线程 TCB 结构体的地址和内核 LWP 号不是一个概念主线程如果提前退出调用 exit/return整个进程会终止所有线程都会被强制退出。3.3 多线程创建 – 构建任务Task.hpp#pragma once #include iostream #include string class Task { public: Task(const std::string who, int x, int y):_x(x), _y(y), _who(who) {} Task() {} void operator()() { std::cout _who execute task: _x _y _x _y std::endl; } ~Task() {} private: int _x; int _y; std::string _who; };testThread.cc#include iostream #include cstdio #include pthread.h #include unistd.h #include sys/types.h #include string #include vector int g_size 64; void* threadrun(void* arg) { std::string name static_castconst char*(arg); delete[] (char*)arg; while(true) { printf(我是一个新线程, tid: %lu, pid: %d, name: %s\n, pthread_self(), getpid(), name.c_str()); sleep(1); } return nullptr; } // ./CreateThread threadnum; int main(int argc, char* argv[]) { if(argc ! 2) { printf(Usage: %s threadnum\n, argv[0]); return -1; } int threadnum std::stoi(argv[1]); std::vectorpthread_t tids; for(int i 0; i threadnum; i) { pthread_t tid; // char threadname[g_size]; // 这个是不太行的, 如果要让每个线程都有自己的名称, 则需要动态分配内存, 不能使用栈内存 char* threadname (char*)malloc(g_size); snprintf(threadname, g_size, thread-%d, i 1); pthread_create(tid, NULL, threadrun, (void*)threadname); // sleep(1); 这个是不可以完全解决这个问题的 tids.push_back(tid); } sleep(10); for(auto tid : tids) { printf(main for 创建新进程成功, new tid: %lu, main tid: %lu, pid: %d\n, tid, pthread_self(), getpid()); } // 主线程 while(true) { std::cout main thread is running... std::endl; sleep(1); } return 0; }任务构建版// 构建一个任务进行测试 #include iostream #include cstdio #include pthread.h #include unistd.h #include sys/types.h #include string #include vector #include Task.hpp int g_size 64; void* threadrun(void* arg) { Task *t static_castTask*(arg); sleep(1); (*t)(); sleep(1); while(true) { sleep(1); // printf(我是一个新线程, tid: %lu, pid: %d, name: %s\n, pthread_self(), getpid(), name.c_str()); // sleep(1); } return nullptr; } // ./CreateThread threadnum; int main(int argc, char* argv[]) { if(argc ! 2) { printf(Usage: %s threadnum\n, argv[0]); return -1; } int threadnum std::stoi(argv[1]); std::vectorpthread_t tids; for(int i 0; i threadnum; i) { pthread_t tid; char threadname[g_size]; // 这个是不太行的, 如果要让每个线程都有自己的名称, 则需要动态分配内存, 不能使用栈内存 // char* threadname (char*)malloc(g_size); snprintf(threadname, g_size, thread-%d, i 1); Task *t new Task(threadname, 10 i, 20 * i); pthread_create(tid, NULL, threadrun, (void*)t); tids.push_back(tid); sleep(1); } sleep(10); for(auto tid : tids) { printf(main for 创建新进程成功, new tid: %lu, main tid: %lu, pid: %d\n, tid, pthread_self(), getpid()); } // 主线程 while(true) { std::cout main thread is running... std::endl; sleep(1); } return 0; }3.4 线程终止3 种合法终止方式如果需要只终止某个线程而不终止整个进程有 3 种安全合法的方式严禁在线程内调用 exit ()会导致整个进程退出。3.4.1 方式 1线程入口函数 return 返回最推荐的方式线程入口函数执行完毕 return线程自动终止返回值可被pthread_join获取。// 测试线程退出 // 1. return 退出线程函数 #include iostream #include pthread.h #include unistd.h #include sys/types.h #include string const int g_size 64; void* threadrun(void* arg) { std::string name static_castconst char*(arg); int cnt 5; while(cnt) { printf(我是一个新进程: tid: 0x%lx, pid: %d, name: %s, cnt: %d\n, pthread_self(), getpid(), name.c_str(), cnt); cnt--; sleep(1); return nullptr; } // 新进程退出 // 只要自己的线程函数跑完,线程自然退出了 -- 调用return,表示线程退出 // return nullptr; } int main() { pthread_t tid; char threadname[g_size]; snprintf(threadname, g_size, thread-%d, 1); pthread_create(tid, NULL, threadrun, (void*)threadname); while(true) pause(); return 0; }3.4.2 方式 2pthread_exit线程主动终止自身线程内调用该函数主动终止自身效果和 return 一致返回值可被pthread_join获取。函数原型void pthread_exit(void *value_ptr);参数value_ptr线程退出的返回值不能指向线程栈内的局部变量无返回值调用后线程直接终止不会再返回。代码示例void *threadrun(void *args) { std::string namestatic_castconst char *(args); int cnt5; while (cnt) { sleep(1); printf(我是一个新线程:tid: 0x%lx,pid: %d,name: %s,cnt: %d\n, pthread_self(), getpid(),name.c_str(),cnt); cnt--; sleep(1); // return nullptr; } // 新进程退出 // 只要自己的线程函数跑完线程就自然退出了 --调用return表示线程退出 // return nullptr; pthread_exit(nullptr); // exit(3); // 不能用来终止线程它是用来终止进程的多线程中任意一个线程调用exit都表示整个进程退出 }3.4.3 方式 3pthread_cancel终止同进程的其他线程一个线程调用该函数取消同一个进程内的另一个线程被取消的线程默认退出码是PTHREAD_CANCELED值为 - 1。函数原型int pthread_cancel(pthread_t thread);参数thread要终止的目标线程 ID返回值成功返回 0失败返回错误码。代码示例#include iostream #include pthread.h #include unistd.h #include sys/types.h #include string const int g_size 64; void* threadrun(void* arg) { std::string name static_castconst char*(arg); int cnt 5; while(cnt) { printf(我是一个新进程: tid: 0x%lx, pid: %d, name: %s, cnt: %d\n, pthread_self(), getpid(), name.c_str(), cnt); cnt--; sleep(1); // pthread_cancel(pthread_self()); // 自己取消自己 } return (void*)10; } int main() { pthread_t tid; char threadname[g_size]; snprintf(threadname, g_size, thread-%d, 1); pthread_create(tid, NULL, threadrun, (void*)threadname); int n pthread_cancel(tid); if(n ! 0) { printf(pthread_cancel error: %d\n, n); } else { printf(pthread_cancel success\n); } void* ret nullptr; pthread_join(tid, ret); printf(join 0x%lx success, ret code: %lld\n, tid, (long long)ret); while(true) pause(); return 0; }3.5 线程等待pthread_join线程退出后其资源不会被自动释放必须通过pthread_join等待线程退出回收资源否则会造成内存泄漏。函数原型int pthread_join(pthread_t thread, void **value_ptr);参数详解thread要等待的目标线程 IDvalue_ptr输出型参数用于接收线程的退出返回值。如果不关心退出码传 NULL 即可。返回值成功返回 0失败返回错误码线程退出码的 3 种情况线程通过return返回value_ptr指向的是线程return的返回值线程通过pthread_exit终止value_ptr指向的是pthread_exit传入的参数线程被pthread_cancel取消value_ptr指向的是常数PTHREAD_CANCELED。完整实战代码线程等待全场景void *threadrun(void *args) { std::string namestatic_castconst char *(args); int cnt5; while (cnt) { sleep(1); printf(我是一个新线程:tid: 0x%lx,pid: %d,name: %s,cnt: %d\n, pthread_self(), getpid(),name.c_str(),cnt); cnt--; sleep(1); // return nullptr; // pthread_exit(nullptr); } return (void*)0; } int main() { pthread_t tid; char threadname[gsize]; snprintf(threadname,gsize,thread-%d,1); pthread_create(tid, nullptr, threadrun, (void*)threadname); void *retnullptr; pthread_join(tid,ret); printf(join %lx success,ret code: %lld\n,tid,(long long)ret); return 0; }3.6 多线程的优化写法——线程等待Task.hpp#pragma once #include iostream #include string class Task { public: Task(const std::string who, int x, int y):_x(x), _y(y), _who(who) {} Task() {} void Execute() { _result _x _y; } std::string GetResult() { return std::to_string(_x) std::to_string(_y) std::to_string(_result); } ~Task() {} private: int _x; int _y; int _result; std::string _who; };testThread.cc// 多线程的优化 #include iostream #include cstdio #include pthread.h #include unistd.h #include sys/types.h #include string #include vector #include Task.hpp int g_size 64; void* threadrun(void* arg) { Task *t static_castTask*(arg); t-Execute(); return t; } // ./CreateThread threadnum; int main(int argc, char* argv[]) { if(argc ! 2) { printf(Usage: %s threadnum\n, argv[0]); return -1; } int threadnum std::stoi(argv[1]); std::vectorpthread_t tids; for(int i 0; i threadnum; i) { pthread_t tid; char threadname[g_size]; // 这个是不太行的, 如果要让每个线程都有自己的名称, 则需要动态分配内存, 不能使用栈内存 // char* threadname (char*)malloc(g_size); snprintf(threadname, g_size, thread-%d, i 1); Task *t new Task(threadname, 10 i, 20 * i); pthread_create(tid, NULL, threadrun, (void*)t); tids.push_back(tid); std::cout create thread threadname success, tid: tid std::endl; // sleep(1); } // 等待所有线程执行完成 std::vectorTask* result_list; for(auto tid : tids) { Task *t nullptr; pthread_join(tid, (void**)t); result_list.push_back(t); std::cout join success: tid std::endl; } // 处理结果清单 std::cout result list: std::endl; for(auto t : result_list) { std::cout t-GetResult() std::endl; } return 0; }3.7 线程分离pthread_detach默认情况下线程是joinable状态退出后必须通过pthread_join回收资源。如果我们不关心线程的返回值不想阻塞等待线程退出可以将线程设置为分离状态线程退出后系统会自动回收其资源无需手动 join。函数原型int pthread_detach(pthread_t thread);参数thread要分离的目标线程 ID返回值成功返回 0失败返回错误码。使用方式其他线程分离主线程创建子线程后调用pthread_detach(tid)分离子线程线程自分离子线程内调用pthread_detach(pthread_self())分离自身完整实战代码线程分离#include stdio.h #include pthread.h #include unistd.h #include string.h #include stdlib.h // 线程自分离示例 void* thread_detach_self(void* arg) { // 线程自分离 pthread_detach(pthread_self()); printf(子线程已完成自分离运行3秒后退出\n); sleep(3); printf(子线程退出系统自动回收资源\n); pthread_exit(NULL); } int main() { pthread_t tid; int ret pthread_create(tid, NULL, thread_detach_self, NULL); if (ret ! 0) { fprintf(stderr, 创建线程失败%s\n, strerror(ret)); exit(1); } printf(主线程子线程已创建无需join等待\n); // 尝试join分离的线程会直接失败 ret pthread_join(tid, NULL); if (ret ! 0) { fprintf(stderr, join分离线程失败%s\n, strerror(ret)); } // 主线程等待子线程退出否则进程退出会强制结束线程 sleep(4); printf(主线程退出\n); return 0; }四、总结线程进阶的核心逻辑线程进阶的本质是从“使用线程”到“管理线程”的转变。资源划分的核心是“隔离”——通过业务、资源、任务类型的拆分减少竞争、提升资源利用率线程控制的核心是“协作”——通过生命周期管理、顺序控制、中断处理确保线程按预期执行避免失控。实际开发中没有绝对最优的资源划分和线程控制方案需结合业务场景、并发量、硬件资源灵活调整。记住好的多线程设计不是“线程越多越好”而是“资源分配合理、线程管控精准”既能发挥并发优势又能保证系统稳定。