上一篇我们明确了核心定义:
- 进程是操作系统资源分配的基本单位
- 线程是 CPU 调度的最小单位,是进程内部的执行分支,Linux 下本质是轻量级进程(LWP)
而想要真正用好线程,必须先搞懂两个核心问题:
- 同进程内的线程,哪些资源共享?哪些资源私有?
- 如何通过系统接口,完成线程的全生命周期控制?
本篇就围绕这两个问题,把知识点讲透、代码写全。\
一. 进程 VS 线程:资源共享与私有划分
线程的 "轻量化 ",本质是 共享了进程的绝大部分资源,仅私有运行必需的最小上下文。我们先把共享与私有资源划清,这是理解线程所有特性的基础。
1.1 核心定位再梳理(整体图示解析)
- 进程 = 内核数据结构(task_struct/mm_struct 等) + 私有的代码和数据,承担系统资源分配
- 线程 = 进程内部的执行分支,粒度更细、更轻,仅需维护自身运行上下文
- 内核通过
task_struct统一管理进程和线程,线程与进程的核心差异,就在于资源是否共享。


1.2 线程间共享的进程资源
同进程内的所有线程,共享进程地址空间内的绝大部分资源,无需额外申请,这也是线程创建 / 切换成本极低的核心原因:
- 地址空间核心段:代码段(Text Segment)、数据段(Data Segment)、堆区、共享区
- 全局变量、静态变量、堆内申请的内存,所有线程都可直接访问修改
- 文件系统相关:文件描述符表、当前工作目录、用户 id / 组 id
- 一个线程打开的文件,其他线程可直接读写
- 信号相关:每种信号的处理方式(SIG_IGN/SIG_DFL/ 自定义处理函数)
- 信号是发给进程的,任意线程收到信号,都会触发整个进程的信号处理函数
- 进程内核数据:页表、mm_struct 内存描述符、vm_area_struct 内存区域结构
1.3 线程私有的独立资源
线程要独立被 CPU 调度执行,必须私有运行时的上下文数据,这些资源线程间完全隔离,互不影响:
- 线程 ID(TID):用户态 pthread 库的唯一标识,内核态对应 LWP 号
- 寄存器上下文:CPU 寄存器的值,线程切换时保存 / 恢复,保证执行流不混乱
- 独立栈空间:每个线程有自己的私有栈,存放局部变量、函数调用栈帧,互不干扰
- errno 变量:系统调用错误码,线程私有,避免多线程间错误码互相覆盖
- 信号屏蔽字:每个线程可独立设置信号屏蔽规则,不影响其他线程
- 调度优先级:线程可独立设置调度优先级,由内核单独调度
- 线程局部存储(TLS):线程私有的全局变量,仅当前线程可访问
1.4 关键问题:为什么一个线程崩溃,整个进程都会退出?
这是面试高频考点,结合资源划分就能瞬间理解:
- 线程触发异常(除零、野指针、非法内存访问),内核会向进程发送致命信号,而非单独发给线程;
- 信号的处理方式是进程级共享的,进程收到致命信号后,会直接终止整个进程;
- 进程终止后,地址空间、文件描述符等所有资源都会被回收,所有线程自然随之退出。

上篇博客中提到过,更详细的解析可以去上篇博客中看看
二. Linux 线程控制:从创建到回收全流程
Linux 内核仅提供轻量级进程的创建能力,我们日常使用的线程接口,都来自POSIX 标准的 pthread 线程库 ,所有接口都以pthread_开头,编译时必须通过-lpthread链接线程库。
2.1 前置说明:pthread 线程库
- 头文件:必须包含
<pthread.h> - 编译命令:
gcc xxx.c -o xxx -lpthread - 核心特点:线程库运行在用户态,负责线程的管理(TCB 线程控制块、线程栈分配),底层通过
clone系统调用让内核创建 LWP(其实我们之前学习的fork也是这样的)。 - 核心特点:线程库运行在用户态,负责线程的管理(TCB 线程控制块、线程栈分配),底层通过
clone系统调用让内核创建 LWP(其实我们之前学习的fork也是这样的)。

- 代码演示:
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>
#include <sys/wait.h>
#include <signal.h>
// 子线程函数(LWP执行的函数)
int thread_func(void *arg)
{
int thread_id = *(int *)arg;
int count = 0;
printf("LWP %d (PID: %d, 父进程: %d) 开始执行\n",
thread_id, getpid(), getppid());
for (int i = 1; i <= 5; i++)
{
printf("LWP %d 计数: %d\n", thread_id, i);
sleep(1);
}
printf("LWP %d 执行完毕\n", thread_id);
return thread_id * 10; // 返回退出码
}
int main()
{
const int NUM_THREADS = 5;
void **stacks; // 栈空间指针数组
int *thread_ids; // 线程ID数组
pid_t *pids; // LWP的PID数组
int stack_size = 1024 * 1024; // 每个栈1MB
// 分配内存
stacks = (void **)malloc(NUM_THREADS * sizeof(void *));
thread_ids = (int *)malloc(NUM_THREADS * sizeof(int));
pids = (pid_t *)malloc(NUM_THREADS * sizeof(pid_t));
if (!stacks || !thread_ids || !pids)
{
perror("malloc失败");
exit(1);
}
printf("父进程 PID: %d\n\n", getpid());
// 创建5个LWP
for (int i = 0; i < NUM_THREADS; i++)
{
// 为每个LWP分配独立的栈空间(向下增长,分配在堆上)
stacks[i] = malloc(stack_size);
if (!stacks[i])
{
perror("栈分配失败");
exit(1);
}
thread_ids[i] = i + 1;
// 使用clone创建LWP
// CLONE_VM: 共享内存空间(父子进程共享地址空间)
// CLONE_FS: 共享文件系统信息
// CLONE_FILES: 共享文件描述符表
// CLONE_SIGHAND: 共享信号处理器
// CLONE_THREAD: 放入同一线程组(与父进程共享PID)
// SIGCHLD: 子进程退出时发送信号
pids[i] = clone(thread_func,
stacks[i] + stack_size, // 栈顶(栈向下增长)
CLONE_VM | CLONE_FS | CLONE_FILES |
CLONE_SIGHAND | CLONE_THREAD | SIGCHLD,
&thread_ids[i]);
if (pids[i] == -1)
{
perror("clone失败");
exit(1);
}
printf("创建 LWP %d, LWP ID: %d\n", thread_ids[i], pids[i]);
}
printf("\n所有LWP已创建, 等待执行完成...\n");
printf("注意:由于共享内存,主进程需要等待一段时间\n\n");
// 注意:因为使用了CLONE_THREAD,不能使用waitpid等待
// 简单的做法是等待足够长时间让子线程完成
sleep(8);
printf("\n主进程等待结束, 清理资源\n");
// 清理栈空间
for (int i = 0; i < NUM_THREADS; i++)
{
free(stacks[i]);
}
free(stacks);
free(thread_ids);
free(pids);
return 0;
}
2.2 线程创建:pthread_create
用于创建一个新的用户态线程,是线程控制最基础的接口。
函数原型:
cpp
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg);
参数详解:
| 参数 | 作用说明 |
|---|---|
thread |
输出型参数,用于返回新创建线程的用户态线程 ID |
attr |
线程属性设置,传 NULL 表示使用默认属性(栈大小、调度策略等) |
start_routine |
函数指针,线程的入口执行函数,线程创建成功后,会从这个函数开始执行 |
arg |
传递给线程入口函数的参数 |
返回值:
-
成功:返回 0
-
失败:直接返回错误码(不会设置 errno),无需通过 perror 打印,用
strerror(ret)解析错误信息 -
图示中的测试代码:
cpp
#include <iostream>
#include <stdio.h>
#include <string>
#include <unistd.h>
#include <pthread.h>
// int g_val = 100;
// int *p = nullptr;
// void hello(const std::string &name) {
// printf("haha, I am common function!, %s\n", name.c_str());
// sleep(5);
// }
class Task
{
public:
Task() = default;
void operator()()
{
}
~Task() = default;
private:
};
void *threaddrun1(void *args)
{
// p = (int*)malloc(sizeof(int) * 10);
std::string threadname = static_cast<const char*>(args);
while(true)
{
sleep(1);
std::cout << threadname << std::endl;
// printf("%s is running, g_val: %d, &g_val: %p\n", threadname.c_str(), g_val, &g_val);
// sleep(1);
// hello(threadname);
}
}
void* threaddrun2(void *args)
{
std::string threadname = static_cast<const char*>(args);
while(true)
{
sleep(1);
std::cout << threadname << std::endl;
// printf("%s is running, g_val: %d, &g_val: %p\n", threadname.c_str(), g_val, &g_val);
// sleep(1);
// g_val++;
// hello(threadname);
}
}
int main()
{
pthread_t t1, t2;
// 这里也可以定义类 Task t; -- 然后传进去
pthread_create(&t1, nullptr, threaddrun1, (void*)"thread-1");
pthread_create(&t2, nullptr, threaddrun2, (void*)"thread-2");
// pthread_join(t1, nullptr);
// pthread_join(t2, nullptr);
while(true)
{
// printf("Main thread, thread1 id: %ld, thread2 id: %ld\n", t1, t2);
printf("Main thread, thread1 id: %p, thread2 id: %p\n", t1, t2);
sleep(1);
}
return 0;
}
. Linux 线程底层原理
- Linux 内核没有原生线程概念 ,仅有轻量级进程(LWP),通过
clone()系统调用创建。 fork()创建子进程底层同样封装clone()。- 用户态封装 POSIX 标准
pthread线程库(NPTL),我们写的线程本质就是创建内核 LWP。 - Linux 下用户态线程:内核 LWP = 1:1一对一映射。
2. pthread_create核心创建函数
- 共 4 个参数:
thread:输出参数,返回用户层线程 ID。attr:线程属性,默认填NULL即可。start_routine:线程入口函数(函数指针),新线程执行的代码逻辑。arg:入口函数的入参,void*万能指针,可传递任意类型数据。
- 错误处理特殊规则:成功返回
0,失败直接返回错误码 ,不使用全局errno。
3. 线程 ID pthread_t 的本质
- 打印
pthread_t得到的大数值,和终端ps -aL查到的内核 LWP 编号完全不一致。 pthread_t不是内核 LWP 号,是pthread 线程库自行维护的值。- 本质:转为十六进制后,是一个虚拟内存地址。
- 原理:指向线程库内存中、管理线程信息的结构体(线程控制块)的首地址。
4. 编译避坑细节
- 旧版系统:
pthread不属于默认 C 标准库,编译必须加-lpthread链接选项。 - 新版系统(Ubuntu24.04、glibc2.34+):pthread 已合并进
libc,不加-lpthread也可编译通过。 - 开发建议:为保证跨平台兼容性,始终保留
-lpthread编译选项。

配套函数:pthread_self
cpp
pthread_t pthread_self(void);
功能:获取当前线程的用户态线程 ID,和进程的getpid()作用一致。
完整实战代码:创建线程
cpp
// 测试线程创建
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <string>
void* threadrun(void* arg)
{
std::string name = static_cast<const char*>(arg);
while(true)
{
printf("我是一个新线程, tid: %lu, pid: %d\n", pthread_self(), getpid());
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, threadrun, (void*)"thread-1");
while(true)
{
printf("创建新进程成功, new tid: %lu, main tid: %lu, pid: %d\n", tid, pthread_self(), getpid());
sleep(1);
}
return 0;
}
1. 获取线程 ID:pthread_self() 函数
- 核心功能 :线程运行过程中,获取自身的用户层线程 ID(TID)。
- 返回值 :
pthread_t类型,本质是无符号长整型、线程管理结构体的内存地址。 - 关键区别 :
pthread_create():由父线程调用,用来拿到新建子线程的 IDpthread_self():由当前正在运行的线程调用,获取自己本身的 ID
2. 线程创建核心逻辑
- 线程入口:
threadrun函数,是新线程启动后执行的代码起点。 - 参数传递:利用万能
void*指针向新线程传入任意数据,在线程函数内再强制类型转换还原。 - 并发特性:主线程
main()与新建线程,各自独立循环运行、互不干扰,交替执行输出,实现真正并发。
3. PID 与 TID 核心区别(运行结论)
- PID(进程 ID)
- 同一个进程内,所有线程的 PID 完全相同
- 调用
getpid()获取,代表所属的整个进程
- TID(线程 ID,
pthread_t)- 同一个进程内,每一个线程的 TID 都唯一不同
- 作用:在进程内部,区分、标识不同的独立执行流
| 概念 | 对应函数 / 要素 | 核心作用 |
|---|---|---|
| 创建新线程 | pthread_create |
发起、启动一个全新子线程 |
| 获取自身线程 ID | pthread_self |
线程内部查询自己专属的pthread_t |
| 获取进程标识 | getpid |
获取所属进程 ID,所有线程共用同一个 |
| 线程参数传递 | void* 万能指针 |
跨线程传递任意复杂数据的通用载体 |
最终一句话总结
Linux 多线程,就是同一个进程 PID 之下,拥有多个独立、可并发运行的线程(TID);每个线程都可以单独识别自身身份,共享进程资源、同时独立调度执行。
关键说明:
- 线程的执行顺序由内核调度决定,主线程和新线程谁先执行不固定;
- 用户态的
pthread_t线程 ID,本质是进程地址空间中线程 TCB 结构体的地址,和内核 LWP 号不是一个概念; - 主线程如果提前退出(调用 exit/return),整个进程会终止,所有线程都会被强制退出。
2.3 多线程创建 -- 构建任务(等到线程等待结束还有一个优化)
Task.hpp
cpp
#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
cpp
#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_cast<const 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::vector<pthread_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;
}
任务构建版:
cpp
// 构建一个任务进行测试
#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_cast<Task*>(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::vector<pthread_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;
}
2.4 线程终止:3 种合法终止方式
如果需要只终止某个线程,而不终止整个进程,有 3 种安全合法的方式,严禁在线程内调用 exit (),会导致整个进程退出。
2.4.1 方式 1:线程入口函数 return 返回
最推荐的方式,线程入口函数执行完毕 return,线程自动终止,返回值可被pthread_join获取。
cpp
// 测试线程退出
// 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_cast<const 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;
}
2.4.2 方式 2:pthread_exit:线程主动终止自身
线程内调用该函数,主动终止自身,效果和 return 一致,返回值可被pthread_join获取。
函数原型
cpp
void pthread_exit(void *value_ptr);
- 参数
value_ptr:线程退出的返回值,不能指向线程栈内的局部变量; - 无返回值,调用后线程直接终止,不会再返回。
代码示例:
cpp
// 2. pthread_exit() 退出线程函数
#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_cast<const 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_exit(nullptr);
}
// 新进程退出
pthread_exit(nullptr);
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);
// pthread_exit(nullptr);
while(true)
pause();
return 0;
}
2.4.3 方式 3:pthread_cancel:终止同进程的其他线程
一个线程调用该函数,取消同一个进程内的另一个线程,被取消的线程默认退出码是PTHREAD_CANCELED(值为 - 1)。
函数原型
cpp
int pthread_cancel(pthread_t thread);
- 参数
thread:要终止的目标线程 ID; - 返回值:成功返回 0,失败返回错误码。
代码示例:
cpp
#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_cast<const 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;
}
1. ❌ 绝对禁止的危险操作:子线程调用 exit()
- 错误行为:子线程函数内部执行
exit(数值) - 致命后果:
exit()的作用是销毁整个进程,只要任意一个子线程调用它,主线程 + 所有其他子线程都会立刻全部终止,进程整体崩溃。
2. ✅ 线程安全退出的 3 种正规方式
-
方式一:函数正常
return(最推荐)- 用法:线程执行完业务逻辑,直接
return返回指针结果 - 优点:自然结束生命周期,逻辑清晰,不会影响其他任何线程
- 用法:线程执行完业务逻辑,直接
-
方式二:
pthread_exit()主动终止(线程自结束)- 用法:子线程任意位置调用
pthread_exit(返回指针) - 特点:专门终止当前调用线程;深层嵌套函数中直接退出线程,比逐层 return 高效
- 用法:子线程任意位置调用
-
方式三:
pthread_cancel()被动强制终止(他杀)- 用法:主线程 / 其他线程,通过目标线程 ID,调用该函数强制终结目标线程
- 补充:不推荐线程自己调用
pthread_cancel(pthread_self()),存在延迟、风险高
3. 📌 被取消线程的返回值规则
- 正常主动退出:线程
return (void*)100,主线程 join 可以拿到自定义返回值100 - 被强制
cancel取消:线程无法自定义返回值,系统强制设定退出码为-1 - 底层宏定义:
PTHREAD_CANCELED ((void *) -1),主线程拿到该值,即可判定线程是被强制杀死而非正常结束
4. 💡 线程退出码核心思想
- 线程退出三种场景:正常跑完、运行出错、异常终止
- 自定义
return数值:就是线程的退出码,用来向主线程上报自身任务执行结果 - 引出后续知识点:
pthread_join(),主线程等待线程、接收线程退出码的配套机制
一句话最终总结
正常结束线程优先用return,主动终止用pthread_exit(),强制关停用pthread_cancel()(固定返回 - 1);严禁子线程调用exit(),直接团灭整个进程。
2.5 线程等待:pthread_join(附加:指针和指针变量辨析)
线程退出后,其资源不会被自动释放,必须通过pthread_join等待线程退出,回收资源,否则会造成内存泄漏。
函数原型
cpp
int pthread_join(pthread_t thread, void **value_ptr);
参数详解
thread:要等待的目标线程 ID;value_ptr:输出型参数,用于接收线程的退出返回值。如果不关心退出码,传 NULL 即可。
返回值
- 成功:返回 0
- 失败:返回错误码
线程退出码的 3 种情况
完整实战代码:线程等待全场景
- 线程通过
return返回:value_ptr指向的是线程return的返回值; - 线程通过
pthread_exit终止:value_ptr指向的是pthread_exit传入的参数; - 线程被
pthread_cancel取消:value_ptr指向的是常数PTHREAD_CANCELED。
cpp
// 测试线程等待
#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_cast<const 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 (void*)10;
pthread_exit((void*)100); // 再试试这个
}
int main()
{
pthread_t tid;
char threadname[g_size];
snprintf(threadname, g_size, "thread-%d", 1);
pthread_create(&tid, NULL, threadrun, (void*)threadname);
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;
}
2.6 多线程的优化写法(加上线程等待)
Task.hpp
cpp
#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;
};
cpp
#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;
}
使用方式
完整实战代码:线程分离

-
testThread.cccpp// 多线程的优化 #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_cast<Task*>(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::vector<pthread_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::vector<Task*> 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; }2.7 线程分离:pthread_detach
默认情况下,线程是
joinable状态,退出后必须通过pthread_join回收资源。如果我们不关心线程的返回值,不想阻塞等待线程退出,可以将线程设置为分离状态,线程退出后,系统会自动回收其资源,无需手动 join。函数原型
cppint pthread_detach(pthread_t thread); -
参数
thread:要分离的目标线程 ID; -
返回值:成功返回 0,失败返回错误码。
-
其他线程分离:主线程创建子线程后,调用
pthread_detach(tid)分离子线程; -
线程自分离:子线程内调用
pthread_detach(pthread_self())分离自身。
三. 补充:LWP 与原生线程库的封装关系
很多同学会疑惑:用户态的 pthread 线程,和内核态的 LWP 是什么关系?TCB 又是什么?这里先进行一个简单的讲解,我们后面的博客中还会详细说的
-
内核层:轻量级进程 LWP
- Linux 内核没有线程的概念,只认
task_struct,我们创建的每一个用户态线程,内核都会对应创建一个 LWP(轻量级进程),拥有独立的task_struct,是 CPU 调度的最小单位。
- Linux 内核没有线程的概念,只认
-
用户层:pthread
-
线程库线程库是运行在进程地址空间共享区的动态库,负责用户态线程的管理:
- 为每个线程分配独立栈空间、创建 TCB(线程控制块);
- 维护线程 ID、线程属性、退出码等信息;
- 底层通过
clone系统调用,让内核创建 LWP,建立用户态线程和内核 LWP 的 1:1 对应关系。
-
用户态线程 ID vs 内核 LWP 号
pthread_t:用户态线程 ID,本质是进程地址空间中 TCB 结构体的地址,仅在进程内唯一;- LWP 号:内核态轻量级进程 ID,系统全局唯一,通过
ps -aL可查看,是 CPU 调度的唯一标识。