室友入职离职全手册:线程创建・终止・等待底层逻辑 + 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 -lpthread
3. C++ 标准库实现
C++11 及以上提供了 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 -std=c++11 -pthread注意:std::thread对象析构前必须调用join()或detach(),否则程序直接崩溃。
4. 经典踩坑:给线程传局部变量
很多初学者会写出这样的 bug:在循环里创建线程,把循环变量的地址传进去。
c
运行
// ❌ 错误写法
for(int i=0; i<3; 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::atomic<bool> 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;
}
方案 B:std::promise + std::future(标准异步方案)
对应 C 版本 pthread_exit 携带返回值的能力,是 C++ 官方推荐的线程间安全传值方式。
cpp
运行
#include <iostream>
#include <thread>
#include <future>
void calc_task(int num, std::promise<int> prom) {
int res = num * 2;
prom.set_value(res);
}
int main() {
std::promise<int> prom;
std::future<int> 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;
}
五、线程生命周期全流程图解
我们把创建、运行、终止、等待回收整个流程串起来,形成完整的线程生命周期:

六、全文知识思维导图
最后用一张思维导图把线程控制的核心知识点全部串起来,方便复习巩固:

最后一句话总结
线程创建是给公寓添人,只加私人卧室、复用公共空间,成本极低;线程终止是人走了但资源没清;线程等待是收尾回收、交接成果。 所有线程控制机制,本质都是在「共享地址空间」这个大前提下,平衡并发效率与资源安全。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)