室友入职离职全手册:线程创建・终止・等待底层逻辑 + C/C++ 双语言实战》

发布时间:2026/6/30 16:09:28
室友入职离职全手册:线程创建・终止・等待底层逻辑 + C/C++ 双语言实战》 搞懂了进程地址空间与线程的内存模型我们顺着「合租公寓」的比喻把线程控制的核心三件事一次性讲明白怎么招新室友线程创建、室友怎么退租线程终止、退租后怎么收尾线程等待。搭配流程图、C/C 双语言可运行代码和思维导图看完就能彻底掌握线程的完整生命周期。一、线程创建给公寓招一位新室友线程创建本质就是在现有进程公寓里新增一个独立执行流相当于招一位新室友入住。操作系统会给新线程分配独有的私人资源同时让它共享公寓的所有公共区域。1. 创建时操作系统做了什么你只需要调用一个创建函数背后操作系统会完成四件事在进程地址空间的栈区给新线程分配一块独立的线程栈私人卧室在内核里创建线程控制块TCB记录线程 ID、优先级、寄存器状态等信息把你指定的「入口函数」和「参数」放到新线程的栈里相当于给室友布置任务把线程加入 CPU 调度队列等待分配时间片开始执行这也解释了为什么线程创建比进程快得多进程要复制一整套地址空间相当于盖新公寓而线程只需要加个卧室和工牌公共区域全复用。2. C 语言 POSIX 实现Linux 下标准线程库pthread的创建函数是pthread_create四个参数刚好对应招室友的完整流程c运行int pthread_create( pthread_t *tid, // 输出参数新线程的ID给室友发工牌 const pthread_attr_t *attr, // 线程属性栈大小、优先级等默认填NULL void *(*start_routine)(void *), // 线程入口函数室友的本职工作 void *arg // 传给入口函数的参数给室友的工具/任务 );可运行代码示例创建两个子线程分别执行任务c运行#include stdio.h #include pthread.h #include unistd.h // 线程1的任务打扫卫生 void *clean_task(void *arg) { char *name (char *)arg; printf([%s] 开始打扫公寓...\n, name); sleep(2); printf([%s] 打扫完成\n, name); return NULL; } // 线程2的任务做饭 void *cook_task(void *arg) { char *name (char *)arg; printf([%s] 开始做饭...\n, name); sleep(3); printf([%s] 饭做好了\n, name); return NULL; } int main() { pthread_t t1, t2; pthread_create(t1, NULL, clean_task, 室友A); pthread_create(t2, NULL, cook_task, 室友B); printf(主线程两个室友都开始干活了\n); sleep(5); printf(主线程公寓一天结束\n); return 0; }编译命令gcc demo.c -o demo -lpthread3. C 标准库实现C11 及以上提供了std::thread底层仍封装 pthread语法更安全易用cpp运行#include iostream #include thread #include unistd.h void clean_task(const std::string name) { std::cout [ name ] 开始打扫公寓... std::endl; sleep(2); std::cout [ name ] 打扫完成 std::endl; } void cook_task(const std::string name) { std::cout [ name ] 开始做饭... std::endl; sleep(3); std::cout [ name ] 饭做好了 std::endl; } int main() { std::thread t1(clean_task, 室友A); std::thread t2(cook_task, 室友B); std::cout 主线程两个室友都开始干活了... std::endl; t1.join(); t2.join(); std::cout 主线程公寓一天结束 std::endl; return 0; }编译命令g demo.cpp -o demo -stdc11 -pthread注意std::thread对象析构前必须调用join()或detach()否则程序直接崩溃。4. 经典踩坑给线程传局部变量很多初学者会写出这样的 bug在循环里创建线程把循环变量的地址传进去。c运行// ❌ 错误写法 for(int i0; i3; i){ pthread_create(tid, NULL, task, i); }用公寓比喻很好理解你在自己床头柜上写了个数字转头就改然后让室友去看这个数字。等室友真正去看的时候数字早就变了甚至你已经离开房间把变量销毁了。✅ 正确做法传值强制转成 void*或者动态分配内存传进去C 中优先按值传参引用必须用std::ref包装且保证变量生命周期长于线程。二、线程终止室友的三种「退租方式」线程终止就是室友结束任务、离开公寓。终止方式有三种体面程度不同但都只会让线程自己离开不会影响公寓和其他室友除非搞破坏。方式 1入口函数 return —— 体面主动退场线程函数执行完return返回相当于室友干完活收拾好东西主动关门走人。这是最推荐、最安全的终止方式返回值可以被其他线程获取局部变量自动销毁。方式 2调用退出函数 —— 随时主动退房在线程函数的任意位置调用退出函数线程立刻终止也可以携带返回值。C 语言pthread_exit(返回值)C函数内任意位置return即可不推荐暴力终止优先用标志位协作退出和return的区别return只是从当前函数返回外层还有函数的话会继续执行pthread_exit直接终止整个线程不管嵌套了多少层函数。方式 3被其他线程取消 —— 被动劝退一个线程可以让另一个线程终止相当于告诉室友 “你别干了可以走了”。C 语言pthread_cancel(tid)注意取消不是立刻生效线程需要走到「取消点」才会真正退出。C标准库无强制取消接口通用做法是用原子标志位协作式退出避免资源泄漏。❌ 绝对禁忌在线程里调用 exit ()exit()是终止整个进程的不是终止线程。就像室友闹脾气直接把整栋公寓炸了所有室友全部强制退场。这是多线程编程里的经典低级错误。C 协作式取消示例cpp运行#include iostream #include thread #include atomic #include unistd.h std::atomicbool is_running true; void work_task() { while(is_running) { std::cout 线程正在工作... std::endl; sleep(1); } std::cout 线程收到退出信号正常退场 std::endl; } int main() { std::thread t(work_task); sleep(3); is_running false; t.join(); std::cout 主线程线程资源已回收 std::endl; return 0; }三、线程等待等室友交接完再收房线程等待的作用是阻塞等待指定线程结束获取它的返回值并回收线程的所有系统资源。用公寓比喻就是室友说要走了你站在卧室门口等他出来接过他交还的钥匙和工作成果返回值然后进去打扫房间、回收床位释放栈和 TCB 资源。如果不等待回收线程会变成「僵尸线程」越积越多最终占满资源。1. C 语言 POSIX 实现函数pthread_join阻塞等待目标线程终止并回收资源c运行int pthread_join( pthread_t tid, // 要等待的线程ID void **retval // 二级指针接收线程的返回值 );基础等待示例c运行int main() { pthread_t t1, t2; pthread_create(t1, NULL, clean_task, 室友A); pthread_create(t2, NULL, cook_task, 室友B); printf(主线程等待室友们干完活...\n); pthread_join(t1, NULL); printf(主线程室友A已退场\n); pthread_join(t2, NULL); printf(主线程室友B已退场\n); printf(主线程所有人都走了公寓关门\n); return 0; }获取返回值示例c运行// 子线程返回计算结果 void *calc_task(void *arg) { int num *(int*)arg; int *result malloc(sizeof(int)); *result num * 2; pthread_exit(result); } // 主线程接收结果 int main() { pthread_t tid; int n 10; pthread_create(tid, NULL, calc_task, n); void *ret; pthread_join(tid, ret); int *res (int*)ret; printf(计算结果%d\n, *res); free(res); return 0; }2. C 标准库实现C 不支持join直接拿返回值推荐两种主流方案方案 A引用传参输出结果简单直观cpp运行#include iostream #include thread void calc_task(int num, int result) { result num * 2; } int main() { int result 0; std::thread t(calc_task, 10, std::ref(result)); t.join(); std::cout 计算结果 result std::endl; return 0; }方案 Bstd::promise std::future标准异步方案对应 C 版本pthread_exit携带返回值的能力是 C 官方推荐的线程间安全传值方式。cpp运行#include iostream #include thread #include future void calc_task(int num, std::promiseint prom) { int res num * 2; prom.set_value(res); } int main() { std::promiseint prom; std::futureint fut prom.get_future(); std::thread t(calc_task, 10, std::move(prom)); int result fut.get(); std::cout 计算结果 result std::endl; t.join(); return 0; }四、线程分离不用等的自主退房如果有些后台线程比如日志线程我们不需要它的返回值也不想专门等它收尾可以设置为分离线程。比喻室友入住时就说好 “我走的时候自己打扫干净房间不用你送”。线程终止后操作系统自动回收资源不需要join。分离后的线程不能再被 join否则报错适合不需要交互的后台常驻线程。C 语言实现c运行pthread_detach(tid); // 设置线程分离C 实现cpp运行#include iostream #include thread #include unistd.h void daemon_task() { while(true) { std::cout [后台线程] 正在巡检公寓... std::endl; sleep(1); } } int main() { std::thread t(daemon_task); t.detach(); std::cout 主线程前台业务运行中... std::endl; sleep(5); std::cout 主线程前台业务结束程序退出 std::endl; return 0; }五、线程生命周期全流程图解我们把创建、运行、终止、等待回收整个流程串起来形成完整的线程生命周期六、全文知识思维导图最后用一张思维导图把线程控制的核心知识点全部串起来方便复习巩固最后一句话总结线程创建是给公寓添人只加私人卧室、复用公共空间成本极低线程终止是人走了但资源没清线程等待是收尾回收、交接成果。所有线程控制机制本质都是在「共享地址空间」这个大前提下平衡并发效率与资源安全。谢谢