
◆ 博主名称: 小此方-CSDN博客
大家好,欢迎来到小此方的博客。
⭐️Linux系列个人专栏:
【主题曲】Linux
⭐️此方的GitHub:
github_此方
⭐️
Re系列专栏:我们思考 (Rethink) · 我们重建 (Rebuild) · 我们记录 (Record)
文章目录
- 概要&序論
- 一、认识 pthread 线程库
-
- 1.1 为什么需要 pthread 库?
- 1.2 多线程的层状封装关系
-
- 1.2.1 底层到用户层的调用链路
- 1.2.2 C++11 等高级语言的跨平台实现
- 二、线程的控制接口
-
- 2.1 线程创建与线程等待
-
- 2.1.1 线程创建:pthread_create详解
- 2.1.2 线程等待:pthread_join
- 2.1.3 获取自身线程 ID:pthread_self
- Tips:线程ID相关误区
- 2.1.4线程创建/等待demo代码
- 2.1.5线程处理任务demo代码
- 2.2 线程终止与线程取消
-
- 2.2.1 线程终止的方式
- 2.2.2 线程取消:pthread_cancel
- 2.3 线程分离:pthread_detach
-
- 2.3.1 为什么需要线程分离?
- 2.3.2 pthread_detach 接口
- 2.4 Tips:一些值得思考的细节问题
-
- 2.4.1 新线程能不能开更小的线程?
- 2.4.2 新线程能不能把主线程取消掉?
- 2.5 线程终止与线程分离demo
-
- 2.5.1线程分离demo
- 2.5.2线程终止demo
概要&序論
Hello大家好,我是此方。在上一篇文章中,我们深入了解了线程的基本概念以及线程与进程之间的关系。本文将正式进入Linux多线程编程实战,学习pthread线程库提供的核心控制接口,包括线程创建、等待、退出、取消以及线程分离。通过这些接口,我们将进一步理解线程完整生命周期背后的系统机制。
一、认识 pthread 线程库
1.1 为什么需要 pthread 库?
在操作系统层面,Linux 并没有传统意义上真正独立的“线程”概念。内核中只有轻量级进程(LWP, Light Weight Process),内核主要通过 clone 或 vfork 等系统调用来实现地址空间共享的进程创建。 但对于用户来说,开发者只关心“线程”这一抽象概念。为了弥补用户需求与底层实现的鸿沟,Linux 提供了 pthread 库(即 POSIX 线程库,属于原生线程库)。它把底层的轻量级进程系统调用封装起来,直接向用户暴露一套标准的线程创建与控制接口。
1.2 多线程的层状封装关系
Linux 系统的线程实现在用户层(用户级线程),用户无需关心底层的 LWP 细节。它的整体层状关系与跨平台封装逻辑如下:
1.2.1 底层到用户层的调用链路
- 用户层:不同的语言封装了不同的线程库。比如C++11的并发支持库。
- 原生线程库层(pthread):对底层接口进行封装。
- 操作系统层(OS):内核提供 clone 等系统调用,创建与父进程共享地址空间的轻量级进程。

1.2.2 C++11 等高级语言的跨平台实现
像 C++11 中的 std::thread 能够实现跨平台可移植性,其底层也是基于条件编译进行的二次封装:
- Linux 环境下:std::thread 本质上就是对 pthread 库 的进一步封装。
- Windows 环境下:std::thread 则是对 Windows 系统的原生创建线程接口进行的封装。
//举例,C++11的并发支持库
void hello(){
while (true)
std::cout << "新线程: hello world" << std::endl;
}
int main(){
std::thread t(hello);
while (true)
std::cout << "我是主线程…" << ", pid: " << getpid() << std::endl;
t.join();
return 0;
}
在未来我也会在现在C++专栏中讲解C++11的并发支持库的相关内容。
二、线程的控制接口
2.1 线程创建与线程等待
- 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以 “pthread_” 打头的
- 要使用这些函数库,要通过引入头文 <pthread.h>
- 链接这些线程函数库时要使用编译器命令的 “-lpthread” 选项
2.1.1 线程创建:pthread_create详解
函数原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
参数详解:
- thread:输出型参数,用于带回创建成功的线程 ID(tid)。注意:线程 tid 是 pthread 库维持的概念,不要直接与内核的 LWP 概念混淆。
- attr:用于设置线程属性,通常直接传入 nullptr 表示使用默认属性。
- start_routine:函数指针,即新线程要执行的入口函数。
- arg:传递给 start_routine 函数的参数。
返回值:
- 成功时返回 0。
- 失败时返回 错误码(error number),且 *thread 的内容未定义。
- 传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。
- pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通过返回值返回。
- pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值来判定,因为读取返回值要比读取线程内的errno变量的开销更小。
2.1.2 线程等待:pthread_join
新线程创建后,必须被主线程或其它线程等待,否则会造成类似于僵尸进程的问题,导致内存泄露。
函数原型:
int pthread_join(pthread_t thread, void **retval);
参数与作用:
- thread:需要等待的目标线程 ID(即 pthread_create 获取的 thread)。
- retval:输出型参数,用来接收目标线程退出时的返回值。
为什么在join的时候,没有见到异常相关的字段呢
- 等待的目标线程如果发生异常,会导致整个进程崩溃退出(包括 main 线程),因此在 join 时试图捕获线程异常没有任何意义。
- join 接口的设计完全是基于线程正常跑完的情况。它不需要处理异常信号,处理异常信号是属于进程层面的话题!
调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:

2.1.3 获取自身线程 ID:pthread_self
函数原型:
pthread_t pthread_self(void);
作用: 返回调用线程自身的线程 ID(tid)。
Tips:线程ID相关误区
很多人对线程id有所误解,我们打印一个线程ID看看,它其实不是LWP。
打印出来的 tid 是一个 pthread_t 类型的变量,指代的是调用 pthread_self 函数的线程的“ID”。 怎么理解这个“ID”呢?这个“ID”是 pthread 库给每个线程定义的进程内唯一标识,是 pthread 库维持的。 由于每个进程有自己独立的内存空间,故此“ID”的作用域是进程级而非系统级(内核不认识)。 其实 pthread 库也是通过内核提供的系统调用(例如clone)来创建线程的,而内核会为每个线程创建系统全局唯一的“ID”来唯一标识这个线程。
LWP 是什么呢?LWP 得到的是真正的线程ID。之前使用 pthread_self 得到的这个数实际上是一个地址,在虚拟地址空间上的一个地址,通过这个地址,可以找到关于这个线程的基本信息,包括线程ID,线程栈,寄存器等属性。 在 ps -aL 得到的线程ID,有一个线程ID和进程ID相同,这个线程就是主线程,主线程的栈在虚拟地址空间的栈上,而其他线程的栈是在共享区(堆栈之间),因为pthread系列函数都是pthread库提供给我们的。而pthread库是在共享区的。所以除了主线程之外的其他线程的栈都在共享区。(这块儿我们现在提一嘴,放在后面讲)
2.1.4线程创建/等待demo代码
#include <iostream>
#include <pthread.h>
#include <unistd.h>
void* thread_run(void* arg) {
const char* name = (const char*)arg;
for (int i = 0; i < 3; i++) {
std::cout << name << " running, tid: " << pthread_self() << std::endl;
sleep(1);
}
return (void*)100;
}
int main() {
pthread_t tid;
pthread_create(&tid, nullptr, thread_run, (void*)"thread 1");
void* ret = nullptr;
pthread_join(tid, &ret);
std::cout << "thread exit code: " << (long long)ret << std::endl;
return 0;
}

给线程传递的参数和返回值,可以是任意类型,于是自然可以是对象。
2.1.5线程处理任务demo代码
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>
#include <vector>
#include<functional>
using Task = std::function<void(void)> ;
class TaskManager{
private:
std::vector<Task> Taskv;
public:
TaskManager(){};
~TaskManager(){};
void Addtask(const std::function<void(void)>& task){
Taskv.push_back(task);
}
Task Gettask(int i){
return Taskv[i];
}
};
void *Routine(void *args)
{
Task* task = (Task*) args ;
(*task)();
delete task ;
// for (int i = 0; i < 10; i++)
// std::cout << "我是一个线程" << "我的名字是" << args << "我的tid是" << pthread_self() << std::endl;
return (void *)100;
}
void func_1() { std::cout << "线程 [" << pthread_self() << "] 正在执行:网络任务" << std::endl; }
void func_2() { std::cout << "线程 [" << pthread_self() << "] 正在执行:数据库任务" << std::endl; }
void func_3() { std::cout << "线程 [" << pthread_self() << "] 正在执行:系统任务" << std::endl; }
void func_4() { std::cout << "线程 [" << pthread_self() << "] 正在执行:重要任务" << std::endl; }
void func_5() { std::cout << "线程 [" << pthread_self() << "] 正在执行:关键任务" << std::endl; }
int main()
{
TaskManager taskmag ;
taskmag.Addtask(func_1);
taskmag.Addtask(func_2);
taskmag.Addtask(func_3);
taskmag.Addtask(func_4);
taskmag.Addtask(func_5);
std::vector <pthread_t> pvtion ={0};
for (int i = 0; i < 5; i++)
{
Task* task = new Task(taskmag.Gettask(i));
int n = pthread_create(&pvtion[i], NULL, Routine, task);
if (n != 0)
std::cerr << "pthread_create error" << std::endl;
}
for (int i = 0; i < 5; i++)
{
void *ret = 0;
pthread_join(pvtion[i], &ret);
std::cout << (long)ret << std::endl;
}
return 0;
}
2.2 线程终止与线程取消
2.2.1 线程终止的方式
如果需要只终止某个线程&&不终止整个进程,可以有三种方法:
pthread_exit 函数原型:
#include <pthread.h>
void pthread_exit(void *retval);
- retval:线程退出的返回值(同 return retval;),可以被 pthread_join 获取。
需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
2.2.2 线程取消:pthread_cancel
函数原型:
#include <pthread.h>
int pthread_cancel(pthread_t thread);
参数与细节:
- thread:要取消的目标线程 ID。
- 取消前提:取消前必须确保目标线程已经启动运行。
- 返回值:被取消的线程如果被 pthread_join 等待,其带回的退出结果为 PTHREAD_CANCELED(本质上是 (void*)-1)。
2.3 线程分离:pthread_detach
2.3.1 为什么需要线程分离?
- 默认状态:线程默认是可等待的(joinable)。主线程必须对其进行 pthread_join 释放资源,否则会导致资源泄漏。
- 分离状态:如果主线程不关心新线程的返回值,只是希望新线程退出后能自动释放自身资源,则应将线程设置为分离状态(detached)。
- 资源归属:分离后的线程依旧在进程的地址空间中,共享进程的所有资源,只是不再需要也不再允许被主线程 join。
2.3.2 pthread_detach 接口
函数原型:
#include <pthread.h>
int pthread_detach(pthread_t thread);
- 可以由主线程调用 pthread_detach(tid) 分离新线程,也可以由新线程在内部调用 pthread_detach(pthread_self()) 分离自己。
- 注意:一个被分离的线程如果再次被调用 pthread_join,pthread_join 会直接报错返回。
2.4 Tips:一些值得思考的细节问题
2.4.1 新线程能不能开更小的线程?
可以。 在 POSIX 线程模型中,所有线程(包括主线程和新创建的子线程)在权限和地位上都是平等的。任何线程只要具备执行条件,都可以通过调用 pthread_create 来创建新的线程。
2.4.2 新线程能不能把主线程取消掉?
在技术接口层面可以,但逻辑上极不推荐。
- 技术可行性:任何线程只要获取到了主线程的线程 ID(可以通过参数将主线程的 pthread_self() 传给子线程),都可以对主线程调用 pthread_cancel。
- 实际影响:主线程被取消后,主线程会退出运行,但只要进程中还有其他未分离或未退出的子线程在运行,整个进程就不会立即终止;直到所有线程都退出,进程才会真正终止。这种做法极易引发资源回收逻辑混乱,破坏程序设计的正常生命周期管理。
2.5 线程终止与线程分离demo
2.5.1线程分离demo
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <cstring>
void* thread_detach_demo(void* arg) {
pthread_detach(pthread_self()); // 自我分离
std::cout << "detached thread running…" << std::endl;
sleep(1);
return nullptr;
}
int main() {
pthread_t tid;
pthread_create(&tid, nullptr, thread_detach_demo, nullptr);
sleep(2); // 确保子线程运行完成
// 尝试 join 已分离的线程
int ret = pthread_join(tid, nullptr);
if (ret != 0) {
std::cout << "pthread_join failed: " << strerror(ret) << std::endl;
}
return 0;
}
2.5.2线程终止demo
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <cstring>
void* thread_exit_demo(void* arg) {
std::cout << "new thread running…" << std::endl;
sleep(1);
pthread_exit((void*)200); // 终止自身并返回 200
}
int main() {
pthread_t tid;
pthread_create(&tid, nullptr, thread_exit_demo, nullptr);
void* ret = nullptr;
pthread_join(tid, &ret);
std::cout << "thread exit code: " << (long long)ret << std::endl;
return 0;
}
好的本期内容就到这里,如果对你有帮助,还不要忘记点赞三联支持。我是此方,我们下期再见。bye! Linux、C++、算法持续连载中,欢迎关注WeChat Official Account 【此方的技术栈】。
网硕互联帮助中心



评论前必须登录!
注册