
在 Linux 多线程开发中,多数开发者停留在 POSIX 线程库(pthread)的 API 调用层面,对线程 ID 的本质、进程地址空间布局、线程栈的实现细节知之甚少。而线程封装作为进阶技能,能让我们脱离繁琐的原生 API,以更优雅的方式管理线程生命周期。但要写出高效、安全的封装类,必须先深入理解线程的底层实现逻辑 —— 线程 ID 到底是什么?进程地址空间中线程的栈、线程控制块(TCB)如何分布?pthread 库创建线程的源码流程是怎样的? 本文将从 “线程 ID 与进程地址空间布局” 切入,层层递进解析线程栈的特性、页表与内存管理的关联,最终落地到线程封装的实战实现,让你不仅 “会用” 线程封装,更能 “懂原理”,真正吃透 Linux 线程的底层逻辑。下面就让我们正式开始吧!
提到线程 ID(TID),很多开发者会混淆两个完全不同的概念 —— 用户级线程 ID(pthread_t)和内核级线程 ID(LWP)。这两种 ID 的作用、实现方式和作用域截然不同,是理解线程底层逻辑的第一个关键点。
在 Linux 内核中,并不存在专门的 “线程结构体”,线程本质是轻量级进程(Light Weight Process,LWP),内核通过task_struct结构体统一管理进程和线程。内核为每个task_struct分配的全局唯一标识,就是内核级线程 ID(LWP)。
ps -aL命令查看,输出结果中的LWP列即为内核级线程 ID。gettid()系统调用获取(需包含<sys/syscall.h>头文件)。示例:通过 ps 命令查看线程 LWP
# 编译运行一个多线程程序后,查看线程信息
ps -aL | grep 程序名输出结果:
12345 12345 pts/0 00:00:00 thread_demo # 主线程:PID=12345,LWP=12345
12345 12346 pts/0 00:00:00 thread_demo # 子线程1:PID=12345,LWP=12346
12345 12347 pts/0 00:00:00 thread_demo # 子线程2:PID=12345,LWP=12347代码示例:获取内核级线程 ID(LWP)
#include <stdio.h>
#include <sys/syscall.h>
#include <unistd.h>
int main() {
// gettid()是系统调用,需通过syscall函数调用
pid_t lwp = syscall(SYS_gettid);
printf("内核级线程ID(LWP):%d\n", lwp);
printf("进程ID(PID):%d\n", getpid());
return 0;
} 用户级线程 ID 是 POSIX 线程库(pthread)定义的标识,类型为pthread_t,用于在进程内唯一识别线程。
pthread_t类型,它是线程库自行维护的标识。pthread_create函数的第一个参数输出,获取新创建线程的pthread_t。pthread_self()函数获取当前线程的pthread_t。pthread_t本质是进程地址空间中的一个虚拟地址,指向线程的控制块(TCB,Thread Control Block)结构体。代码示例:获取用户级线程 ID(pthread_t)
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *thread_func(void *arg) {
// 获取当前线程的用户级ID
pthread_t tid = pthread_self();
printf("子线程:用户级线程ID = %lu(本质是虚拟地址)\n", (unsigned long)tid);
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
printf("主线程:子线程用户级ID = %lu\n", (unsigned long)tid);
pthread_join(tid, NULL);
return 0;
}编译运行:
gcc pthread_t_demo.c -o pthread_t_demo -lpthread
./pthread_t_demo输出结果:
主线程:子线程用户级ID = 140703347508992
子线程:用户级线程ID = 140703347508992(本质是虚拟地址)特性 | 内核级线程 ID(LWP) | 用户级线程 ID(pthread_t) |
|---|---|---|
定义者 | Linux 内核 | POSIX 线程库(pthread) |
作用域 | 系统全局唯一 | 进程内唯一 |
本质 | 整数(pid_t 类型) | 虚拟地址(指向 TCB) |
内核是否识别 | 是(调度的唯一标识) | 否(仅线程库使用) |
获取方式 | syscall(SYS_gettid)、ps -aL | pthread_create、pthread_self() |
理解这两种 ID 的区别,能避免很多多线程开发中的坑。例如,不能用pthread_t作为系统级标识传递给其他进程,因为它在进程外没有意义;而 LWP 是系统全局唯一的,可以用于跨进程识别线程。
要理解线程的运行机制,必须清楚线程在进程地址空间中的分布 —— 线程的栈、线程控制块(TCB)、线程局部存储(TLS)等都位于进程地址空间的特定区域。Linux 进程的虚拟地址空间从低地址到高地址分为以下区域:代码段、已初始化数据段、未初始化数据段、堆区、共享区(mmap 区域)、栈区、内核空间。
mmap系统调用分配,大小固定(默认通常为 8MB),不支持动态增长,栈空间用尽会直接触发段错误。子线程栈位于共享区的原因:pthread 库是动态链接库,加载到进程的共享区,子线程由 pthread 库创建,其栈空间也分配在共享区,方便线程库管理。
线程控制块(TCB)是 pthread 库维护的结构体(在 glibc 源码中为struct pthread),存储线程的所有状态信息(线程 ID、栈地址、调度优先级、取消状态等)。TCB 位于子线程栈的末端,pthread_t就是该结构体的虚拟地址。
线程局部存储(Thread Local Storage)是线程的私有数据区域,用于存储线程独有的全局变量(如__thread修饰的变量),位于共享区,每个线程有独立的 TLS 空间,互不干扰。

通过代码打印主线程栈和子线程栈的地址,验证它们的分布区域:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 主线程栈变量(位于栈区)
int main_stack_var;
void *thread_func(void *arg) {
// 子线程栈变量(位于共享区)
int thread_stack_var;
printf("子线程:栈变量地址 = %p(共享区)\n", &thread_stack_var);
printf("子线程:TCB地址(pthread_t) = %p\n", (void*)pthread_self());
return NULL;
}
int main() {
printf("主线程:栈变量地址 = %p(栈区)\n", &main_stack_var);
printf("主线程:全局变量地址 = %p(数据段)\n", &main_stack_var);
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
pthread_join(tid, NULL);
return 0;
}运行结果:
主线程:栈变量地址 = 0x7ffeefbff5ac(栈区,高地址)
主线程:全局变量地址 = 0x55f8d7a7c010(数据段,低地址)
子线程:栈变量地址 = 0x7f6b3a7fc7ac(共享区,地址介于堆和栈之间)
子线程:TCB地址(pthread_t) = 0x7f6b3a7fd700从结果可以看出,主线程栈变量地址(0x7ffe 开头)高于子线程栈变量地址(0x7f6b 开头),验证了主线程栈位于栈区(更高地址),子线程栈位于共享区。
线程栈是线程的私有数据区域,用于存储局部变量、函数调用参数和返回值,其实现细节直接影响线程的稳定性和安全性。
特性 | 主线程栈 | 子线程栈 |
|---|---|---|
分配方式 | 进程创建时内核自动分配 | pthread 库通过mmap系统调用分配 |
存储区域 | 栈区 | 共享区(mmap 区域) |
增长方向 | 从高地址向低地址 | 从高地址向低地址 |
是否支持动态增长 | 是(默认 8MB,可通过ulimit调整) | 否(大小固定,默认 8MB) |
栈溢出表现 | 超出上限触发段错误(SIGSEGV) | 栈空间用尽触发段错误(SIGSEGV) |
子线程栈的分配逻辑位于 glibc 的nptl/allocatestack.c文件的allocate_stack函数中,核心步骤如下:
获取线程栈大小(用户通过pthread_attr_setstacksize设置,未设置则使用默认值 8MB)。
尝试从 pthread 库的栈缓存中分配栈空间,缓存命中则直接使用。
缓存未命中则调用mmap系统调用分配匿名内存(私有、匿名、栈类型):
mem = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);在分配的栈空间末端设置线程控制块(TCB)。
设置栈保护区域(Guard Page),通过mprotect设置为PROT_NONE,防止栈溢出访问到其他内存区域。
关键源码片段(glibc-2.4):
// 分配栈空间(allocatestack.c)
mem = mmap(NULL, size, prot, MAP_PRIVATE | MAP_ANONYMOUS | ARCH_MAP_FLAGS, -1, 0);
if (mem == MAP_FAILED) {
// 分配失败处理
return errno;
}
// 在栈末端放置TCB(TLS_TCB_AT_TP模式)
pd = (struct pthread *)((char *)mem + size - coloring) - 1;
// 设置栈保护区域(Guard Page)
char *guard = mem;
if (mprotect(guard, guardsize, PROT_NONE) != 0) {
// 保护区域设置失败处理
munmap(mem, size);
return errno;
}子线程栈大小固定,若局部变量过大(如大型数组)或递归调用过深,会导致栈溢出,触发段错误。
错误示例:子线程栈溢出
#include <stdio.h>
#include <pthread.h>
void *thread_overflow(void *arg) {
// 定义10MB的局部数组,超出默认8MB栈大小,触发栈溢出
char big_array[1024 * 1024 * 10];
printf("子线程:尝试使用10MB局部数组(栈溢出)\n");
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_overflow, NULL);
pthread_join(tid, NULL);
return 0;
}运行结果:
Segmentation fault (core dumped) # 栈溢出触发段错误解决方案:
malloc/new)。pthread_attr_setstacksize设置更大的栈大小。正确示例:设置子线程栈大小
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *thread_large_stack(void *arg) {
// 定义10MB局部数组(栈大小已设置为16MB)
char big_array[1024 * 1024 * 10];
printf("子线程:成功使用10MB局部数组\n");
return NULL;
}
int main() {
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
// 设置栈大小为16MB(16*1024*1024字节)
size_t stack_size = 16 * 1024 * 1024;
pthread_attr_setstacksize(&attr, stack_size);
pthread_create(&tid, &attr, thread_large_stack, NULL);
pthread_join(tid, NULL);
pthread_attr_destroy(&attr);
return 0;
}子线程栈是线程私有区域,但同一进程的其他线程可以通过指针访问到该栈(因为共享进程地址空间),可能导致数据竞争或非法访问。
示例:线程间访问栈数据(不推荐)
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int *g_thread_stack_ptr; // 全局指针,指向子线程栈变量
void *thread_func(void *arg) {
int thread_var = 100;
g_thread_stack_ptr = &thread_var; // 将子线程栈变量地址赋值给全局指针
sleep(2); // 等待主线程访问
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
sleep(1); // 等待子线程初始化变量
// 主线程通过全局指针访问子线程栈变量(存在风险)
printf("主线程:访问子线程栈变量 = %d\n", *g_thread_stack_ptr);
pthread_join(tid, NULL);
return 0;
}注意:这种做法存在严重风险,若子线程退出后主线程再访问该指针,会导致野指针访问(子线程栈已被释放)。线程间数据传递应使用全局变量、堆内存或线程安全的队列,避免直接访问对方栈空间。
线程共享进程的虚拟地址空间,本质是共享进程的页表(Page Table)。页表是虚拟地址到物理地址的映射表,由 MMU(内存管理单元)硬件解析,是线程共享内存资源的底层支撑。
Linux 采用多级页表(32 位系统为二级页表,64 位系统为四级页表),避免单级页表占用过多连续物理内存。以 32 位系统为例:
页表项(pte_t)是页表中的基本单元,存储虚拟页到物理页框的映射关系和访问权限,定义在include/linux/mm_types.h中:
typedef struct { unsigned long pte; } pte_t; // 页表项
typedef struct { unsigned long pgd; } pgd_t; // 页目录项 页表项的关键标志位(定义在include/linux/pgtable.h):
L_PTE_PRESENT(1<<0):页面是否在物理内存中。L_PTE_WRITE(1<<5):页面是否可写。L_PTE_EXEC(1<<6):页面是否可执行。L_PTE_DIRTY(1<<7):页面是否被修改(脏页)。L_PTE_USER(1<<4):用户态是否可访问。同一进程的所有线程共享同一个页目录表(PGD)和页表(PTE),因此:
页表本身是线程共享的,但 MMU 在访问页表时是原子操作,不会出现数据竞争。但线程访问共享内存中的数据时,可能出现数据竞争,需要通过互斥锁等同步机制保护,这与页表本身无关,而是线程调度的随机性导致的。
示例:线程共享全局变量(依赖页表共享)
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int g_shared_var = 0; // 全局变量(位于数据段,线程共享)
pthread_mutex_t mutex; // 互斥锁,保护共享变量
void *thread_incr(void *arg) {
for (int i = 0; i < 10000; i++) {
pthread_mutex_lock(&mutex);
g_shared_var++; // 线程共享全局变量,通过页表映射到同一物理页
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main() {
pthread_mutex_init(&mutex, NULL);
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, thread_incr, NULL);
pthread_create(&tid2, NULL, thread_incr, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
printf("共享变量最终值:%d(预期20000)\n", g_shared_var);
pthread_mutex_destroy(&mutex);
return 0;
}编译运行:
gcc shared_var_demo.c -o shared_var_demo -lpthread
./shared_var_demo输出结果:
共享变量最终值:20000(预期20000)该示例中,两个线程通过共享页表访问同一全局变量的物理内存,互斥锁保证了访问的原子性,避免数据竞争。
理解了线程的底层逻辑后,我们可以基于 pthread 库封装一个通用的 C++ 线程类,屏蔽原生 API 的繁琐细节,支持线程创建、启动、等待、分离、设置名称等功能,同时保证线程安全。
Start、Join、Detach、Stop)。#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>
#include <unistd.h>
#include <cstring>
#include <atomic>
namespace ThreadModule {
// 线程状态枚举
enum class ThreadStatus {
NEW, // 未启动
RUNNING, // 运行中
STOPPED // 已停止
};
// 线程封装类
class Thread {
public:
// 线程函数类型(支持任意参数,通过std::function封装)
using ThreadFunc = std::function<void()>;
// 构造函数:传入线程函数和线程名称(可选)
explicit Thread(ThreadFunc func, const std::string& name = "")
: func_(std::move(func)),
name_(name),
tid_(0),
status_(ThreadStatus::NEW),
is_joinable_(true) {
// 自动生成线程名称(若未指定)
if (name_.empty()) {
static std::atomic<uint32_t> thread_cnt(0);
name_ = "Thread-" + std::to_string(thread_cnt++);
}
}
// 析构函数:确保线程资源释放
~Thread() {
// 若线程是joinable状态且未停止,分离线程避免资源泄漏
if (status_ == ThreadStatus::RUNNING && is_joinable_) {
pthread_detach(tid_);
std::cout << "线程[" << name_ << "]:自动分离,避免资源泄漏" << std::endl;
}
}
// 禁用拷贝构造和赋值运算符(线程ID不可拷贝)
Thread(const Thread&) = delete;
Thread& operator=(const Thread&) = delete;
// 移动构造和移动赋值(支持线程对象转移)
Thread(Thread&& other) noexcept
: func_(std::move(other.func_)),
name_(std::move(other.name_)),
tid_(other.tid_),
status_(other.status_),
is_joinable_(other.is_joinable_) {
other.tid_ = 0;
other.status_ = ThreadStatus::NEW;
}
Thread& operator=(Thread&& other) noexcept {
if (this != &other) {
// 释放当前线程资源
if (status_ == ThreadStatus::RUNNING && is_joinable_) {
pthread_detach(tid_);
}
// 转移资源
func_ = std::move(other.func_);
name_ = std::move(other.name_);
tid_ = other.tid_;
status_ = other.status_;
is_joinable_ = other.is_joinable_;
// 重置源对象
other.tid_ = 0;
other.status_ = ThreadStatus::NEW;
}
return *this;
}
// 启动线程
bool Start() {
if (status_ != ThreadStatus::NEW) {
std::cerr << "线程[" << name_ << "]:已启动,无法重复启动" << std::endl;
return false;
}
// 创建线程:传入线程函数和当前对象指针
int ret = pthread_create(&tid_, nullptr, ThreadRoutine, this);
if (ret != 0) {
std::cerr << "线程[" << name_ << "]:创建失败,错误信息:" << strerror(ret) << std::endl;
return false;
}
status_ = ThreadStatus::RUNNING;
std::cout << "线程[" << name_ << "]:启动成功,用户级ID = " << (unsigned long)tid_ << std::endl;
return true;
}
// 等待线程结束(joinable状态下)
bool Join() {
if (!is_joinable_) {
std::cerr << "线程[" << name_ << "]:分离状态,无法等待" << std::endl;
return false;
}
if (status_ != ThreadStatus::RUNNING) {
std::cerr << "线程[" << name_ << "]:未运行或已停止,无需等待" << std::endl;
return false;
}
// 等待线程结束
int ret = pthread_join(tid_, nullptr);
if (ret != 0) {
std::cerr << "线程[" << name_ << "]:等待失败,错误信息:" << strerror(ret) << std::endl;
return false;
}
status_ = ThreadStatus::STOPPED;
std::cout << "线程[" << name_ << "]:已停止,等待完成" << std::endl;
return true;
}
// 分离线程(设置为detached状态)
bool Detach() {
if (!is_joinable_) {
std::cerr << "线程[" << name_ << "]:已分离,无需重复分离" << std::endl;
return false;
}
if (status_ != ThreadStatus::RUNNING) {
std::cerr << "线程[" << name_ << "]:未运行,无法分离" << std::endl;
return false;
}
int ret = pthread_detach(tid_);
if (ret != 0) {
std::cerr << "线程[" << name_ << "]:分离失败,错误信息:" << strerror(ret) << std::endl;
return false;
}
is_joinable_ = false;
std::cout << "线程[" << name_ << "]:分离成功" << std::endl;
return true;
}
// 强制终止线程(谨慎使用)
bool Stop() {
if (status_ != ThreadStatus::RUNNING) {
std::cerr << "线程[" << name_ << "]:未运行,无需终止" << std::endl;
return false;
}
int ret = pthread_cancel(tid_);
if (ret != 0) {
std::cerr << "线程[" << name_ << "]:终止失败,错误信息:" << strerror(ret) << std::endl;
return false;
}
// 等待线程终止并回收资源
if (is_joinable_) {
pthread_join(tid_, nullptr);
}
status_ = ThreadStatus::STOPPED;
std::cout << "线程[" << name_ << "]:已强制终止" << std::endl;
return true;
}
// 获取线程名称
std::string GetName() const { return name_; }
// 获取用户级线程ID
pthread_t GetTid() const { return tid_; }
// 获取线程状态
ThreadStatus GetStatus() const { return status_; }
// 判断线程是否为joinable状态
bool IsJoinable() const { return is_joinable_; }
private:
// 线程入口函数(必须是静态函数,无this指针)
static void* ThreadRoutine(void* arg) {
Thread* thread = static_cast<Thread*>(arg);
if (thread == nullptr) {
std::cerr << "线程入口:参数为空" << std::endl;
return nullptr;
}
// 设置线程名称(Linux非标准函数,用于调试)
pthread_setname_np(pthread_self(), thread->name_.c_str());
try {
// 执行线程函数
if (thread->func_) {
thread->func_();
}
} catch (const std::exception& e) {
std::cerr << "线程[" << thread->name_ << "]:执行异常:" << e.what() << std::endl;
} catch (...) {
std::cerr << "线程[" << thread->name_ << "]:未知异常" << std::endl;
}
// 线程执行完毕,更新状态
thread->status_ = ThreadStatus::STOPPED;
std::cout << "线程[" << thread->name_ << "]:执行完毕" << std::endl;
return nullptr;
}
private:
ThreadFunc func_; // 线程要执行的函数
std::string name_; // 线程名称
pthread_t tid_; // 用户级线程ID
ThreadStatus status_; // 线程状态
bool is_joinable_; // 是否为joinable状态(默认是)
};
} 通过std::function<void()>封装线程函数,结合std::bind或 lambda 表达式,可以传递任意参数的函数:
#include <iostream>
#include <functional>
#include "Thread.hpp"
// 无参数函数
void FuncWithoutArgs() {
std::cout << "无参数线程函数:运行中" << std::endl;
sleep(1);
}
// 单参数函数
void FuncWithOneArg(int num) {
std::cout << "单参数线程函数:num = " << num << std::endl;
sleep(1);
}
// 多参数函数
void FuncWithMultiArgs(const std::string& str, double val) {
std::cout << "多参数线程函数:str = " << str << ", val = " << val << std::endl;
sleep(1);
}
int main() {
using namespace ThreadModule;
// 1. 无参数线程
Thread t1(FuncWithoutArgs, "NoArgThread");
t1.Start();
// 2. 单参数线程(使用std::bind)
Thread t2(std::bind(FuncWithOneArg, 100), "OneArgThread");
t2.Start();
// 3. 多参数线程(使用lambda表达式)
Thread t3([]() {
FuncWithMultiArgs("Hello", 3.14);
}, "MultiArgThread");
t3.Start();
// 4. 带捕获的lambda线程
int x = 200;
Thread t4([x]() {
std::cout << "Lambda线程:捕获x = " << x << std::endl;
sleep(1);
}, "LambdaThread");
t4.Start();
// 等待所有线程结束
t1.Join();
t2.Join();
t3.Join();
t4.Join();
return 0;
} 通过pthread_setname_np函数设置线程名称,方便通过ps -aL或调试工具(如 GDB)识别线程:
# 查看线程名称
ps -aL | grep 程序名输出结果:
12345 12345 pts/0 00:00:00 thread_demo # 主线程
12345 12346 pts/0 00:00:00 NoArgThread # 线程1:NoArgThread
12345 12347 pts/0 00:00:00 OneArgThread # 线程2:OneArgThread
12345 12348 pts/0 00:00:00 MultiArgThread # 线程3:MultiArgThread
12345 12349 pts/0 00:00:00 LambdaThread # 线程4:LambdaThreadjoinable状态且未停止,自动分离线程(pthread_detach),避免资源泄漏。 通过ThreadStatus枚举跟踪线程状态(NEW/RUNNING/STOPPED),避免重复启动、重复等待等错误操作。
#include <iostream>
#include <vector>
#include <chrono>
#include "Thread.hpp"
using namespace ThreadModule;
using namespace std::chrono;
// 计算任务:计算start到end的累加和
void CalcSum(int start, int end, long long& result, pthread_mutex_t& mutex) {
long long sum = 0;
for (int i = start; i <= end; ++i) {
sum += i;
}
// 加锁保护共享结果
pthread_mutex_lock(&mutex);
result += sum;
pthread_mutex_unlock(&mutex);
std::cout << "线程[" << Thread::GetCurrentThreadName() << "]:计算范围[" << start << "," << end << "],局部和 = " << sum << std::endl;
}
int main() {
// 初始化互斥锁
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, nullptr);
// 共享结果
long long total_sum = 0;
// 任务拆分:4个线程计算1~100000000的累加和
const int total = 100000000;
const int thread_num = 4;
const int step = total / thread_num;
std::vector<Thread> threads;
auto start_time = high_resolution_clock::now();
// 创建4个线程
for (int i = 0; i < thread_num; ++i) {
int start = i * step + 1;
int end = (i == thread_num - 1) ? total : (i + 1) * step;
// 绑定任务函数和参数
threads.emplace_back(
[start, end, &total_sum, &mutex]() {
CalcSum(start, end, total_sum, mutex);
},
"CalcThread-" + std::to_string(i)
);
// 启动线程
threads.back().Start();
}
// 等待所有线程结束
for (auto& thread : threads) {
thread.Join();
}
auto end_time = high_resolution_clock::now();
auto duration = duration_cast<milliseconds>(end_time - start_time).count();
// 输出结果
std::cout << "=====================================" << std::endl;
std::cout << "总计算范围:1~" << total << std::endl;
std::cout << "累加和结果:" << total_sum << std::endl;
std::cout << "总耗时:" << duration << "ms" << std::endl;
std::cout << "=====================================" << std::endl;
// 销毁互斥锁
pthread_mutex_destroy(&mutex);
return 0;
}编译运行:
g++ thread_demo.cpp -o thread_demo -lpthread -std=c++11
./thread_demo输出结果:
线程[CalcThread-0]:启动成功,用户级ID = 140703347508992
线程[CalcThread-1]:启动成功,用户级ID = 140703339116288
线程[CalcThread-2]:启动成功,用户级ID = 140703330723584
线程[CalcThread-3]:启动成功,用户级ID = 140703322330880
线程[CalcThread-0]:计算范围[1,25000000],局部和 = 312500012500000
线程[CalcThread-1]:计算范围[25000001,50000000],局部和 = 937500025000000
线程[CalcThread-2]:计算范围[50000001,75000000],局部和 = 1562500037500000
线程[CalcThread-3]:计算范围[75000001,100000000],局部和 = 2187500050000000
线程[CalcThread-0]:执行完毕
线程[CalcThread-1]:执行完毕
线程[CalcThread-2]:执行完毕
线程[CalcThread-3]:执行完毕
线程[CalcThread-0]:已停止,等待完成
线程[CalcThread-1]:已停止,等待完成
线程[CalcThread-2]:已停止,等待完成
线程[CalcThread-3]:已停止,等待完成
=====================================
总计算范围:1~100000000
累加和结果:5000000050000000
总耗时:32ms 要真正理解线程封装的本质,必须深入 pthread 库的源码,看看pthread_create函数是如何创建线程的。以下基于 glibc-2.4 的nptl/pthread_create.c源码,解析线程创建的核心流程。
pthread_create函数首先解析用户传入的线程属性(pthread_attr_t),若用户未指定(传入 NULL),则使用默认属性(default_attr):
// pthread_create.c 源码片段
const struct pthread_attr *iattr = (struct pthread_attr *)attr;
if (iattr == NULL) {
iattr = &default_attr; // 使用默认属性
} 线程属性结构体(struct pthread_attr)包含的关键信息:
struct pthread_attr {
struct sched_param schedparam; // 调度参数(优先级等)
int schedpolicy; // 调度策略
int flags; // 标志位(如分离状态)
size_t guardsize; // 栈保护区域大小
void *stackaddr; // 栈地址(用户指定)
size_t stacksize; // 栈大小
cpu_set_t *cpuset; // CPU亲和性集合
size_t cpusetsize; // CPU亲和性集合大小
}; 通过ALLOCATE_STACK宏调用allocate_stack函数,分配线程栈空间和线程控制块(TCB,struct pthread):
// 分配栈和TCB
struct pthread *pd = NULL;
int err = ALLOCATE_STACK(iattr, &pd);
if (err != 0) {
return err; // 分配失败,返回错误码
} allocate_stack函数的核心工作:
mmap)。struct pthread)。 TCB(struct pthread)是线程的核心数据结构,存储线程的所有状态信息。源码中初始化 TCB 的关键步骤:
// 设置TCB的自引用(TLS相关)
pd->header.self = pd;
pd->header.tcb = pd;
// 存储线程函数和参数
pd->start_routine = start_routine; // 用户传入的线程函数
pd->arg = arg; // 用户传入的线程参数
// 复制调度参数和优先级
pd->schedpolicy = self->schedpolicy;
pd->schedparam = self->schedparam;
// 设置分离状态:若线程属性为分离,则joinid指向自身
pd->joinid = iattr->flags & ATTR_FLAG_DETACHSTATE ? pd : NULL;
// 将TCB地址作为用户级线程ID(pthread_t)返回给用户
*newthread = (pthread_t)pd; 通过create_thread函数调用do_clone,最终调用clone系统调用,请求内核创建轻量级进程(线程):
// 创建线程的核心函数
err = create_thread(pd, iattr, STACK_VARIABLES_ARGS);
if (err != 0) {
// 创建失败,释放资源
__deallocate_stack(pd);
return err;
} clone系统调用的关键参数(设置线程共享资源):
// clone_flags:设置线程共享的资源
int clone_flags = (CLONE_VM | // 共享虚拟地址空间(页表)
CLONE_FS | // 共享文件系统信息
CLONE_FILES | // 共享文件描述符表
CLONE_SIGNAL | // 共享信号处理方式
CLONE_SETTLS | // 设置TLS
CLONE_PARENT_SETTID | // 父进程设置子线程ID
CLONE_CHILD_CLEARTID | // 子线程退出时清除ID
CLONE_SYSVSEM); // 共享System V信号量 clone系统调用成功后,内核创建新的task_struct(轻量级进程),新线程从start_thread函数开始执行,最终调用用户传入的线程函数:
// 线程启动后执行的函数
static int start_thread(void *arg) {
struct pthread *pd = (struct pthread *)arg;
// 执行用户传入的线程函数
void *result = pd->start_routine(pd->arg);
// 线程函数执行完毕,调用pthread_exit
pthread_exit(result);
return 0;
}用户调用pthread_create → 解析线程属性 → 分配栈和TCB → 初始化TCB → 调用clone系统调用 → 内核创建task_struct → 新线程执行start_thread → 调用用户线程函数 → 线程执行完毕调用pthread_exit通过源码解析可以看出,pthread 库的核心工作是:
clone系统调用创建轻量级进程。pthread_join、pthread_detach等)。而我们自己实现的线程封装类,本质是对 pthread 库 API 的进一步封装,让接口更简洁、更符合 C++ 的面向对象编程风格。
学习线程封装与底层原理,不仅能让我们写出更高效、安全的多线程程序,更能帮助我们排查多线程开发中的疑难问题(如栈溢出、野指针、数据竞争等)。后续可以进一步学习线程同步机制(互斥锁、条件变量、信号量)、线程池实现、线程安全设计模式等高级主题,敬请关注! 创作不易,若本文对你有帮助,欢迎点赞、收藏、关注!