目录
[1.1 线程的核心定义](#1.1 线程的核心定义)
[1.2 Linux内核如何实现线程?](#1.2 Linux内核如何实现线程?)
[1.3 线程的核心优势与短板](#1.3 线程的核心优势与短板)
二、进程vs线程:资源共享与独占的边界
[2.1 核心区别:资源分配与调度粒度](#2.1 核心区别:资源分配与调度粒度)
[2.2 资源共享清单](#2.2 资源共享清单)
[2.3 线程独占的"私有财产"](#2.3 线程独占的“私有财产”)
三、虚拟地址空间与线程布局:内存视角的线程
[3.1 分页存储:线程共享地址空间的基础](#3.1 分页存储:线程共享地址空间的基础)
[3.2 线程在地址空间中的位置](#3.2 线程在地址空间中的位置)
[3.3 线程栈与主线程栈的区别](#3.3 线程栈与主线程栈的区别)
四、线程控制实战:创建、终止、等待与分离
[4.1 POSIX线程库:pthread系列函数](#4.1 POSIX线程库:pthread系列函数)
[4.2 线程创建:pthread_create](#4.2 线程创建:pthread_create)
[4.3 线程终止:三种合法方式](#4.3 线程终止:三种合法方式)
[4.4 线程等待:pthread_join的必要性](#4.4 线程等待:pthread_join的必要性)
[4.5 线程分离:pthread_detach](#4.5 线程分离:pthread_detach)
五、线程ID深度解析:两种ID的本质区别
[5.1 内核态线程ID(LWP)](#5.1 内核态线程ID(LWP))
[5.2 用户态线程ID(pthread_t)](#5.2 用户态线程ID(pthread_t))
[5.3 如何查看线程ID?](#5.3 如何查看线程ID?)
六、线程封装:面向对象的实战实现
[6.1 封装思路:线程属性与回调函数](#6.1 封装思路:线程属性与回调函数)
[6.2 完整封装类实现](#6.2 完整封装类实现)
[6.3 封装类使用示例](#6.3 封装类使用示例)
七、总结与进阶建议
一、线程本质:内核视角的"轻量级进程"
线程是进程内部的控制序列,本质是"轻量级进程(LWP)"------在Linux内核中,线程与进程共用一套进程地址空间,通过内核的task_struct结构体管理,但比传统进程更轻量化。
1.1 线程的核心定义
- 线程是调度的基本单位:内核调度的最小单元是线程,而非进程;
- 线程共享进程资源:同一进程的所有线程共享地址空间(代码段、数据段、堆)、文件描述符表、信号处理方式等;
- 线程有私有执行上下文:每个线程有自己的寄存器集合、栈空间、线程ID等,确保执行独立性。
简单说:进程是资源分配的容器,线程是容器内的执行流。一个进程至少有一个主线程,也可以有多个子线程,所有线程在进程的地址空间内并发执行。
1.2 Linux如何实现线程?
Linux内核没有为线程设计独立的数据结构,而是复用了task_struct(进程控制块)------每个线程对应一个task_struct,但多个线程的task_struct指向同一个mm_struct(虚拟地址空间描述符)。

1.3 线程的核心优势与短板
优势:
- 创建成本低:无需分配新的地址空间,仅需创建
task_struct和私有栈; - 切换效率高:线程切换无需刷新TLB(快表),仅需切换寄存器和栈;
- 资源共享便捷:同一进程的线程天然共享堆、全局变量等,无需额外IPC机制。
短板:
- 健壮性低:单个线程崩溃会触发信号终止整个进程;
- 缺乏访问控制:线程无独立资源边界,一个线程的错误操作可能影响所有线程;
- 编程复杂度高:需处理线程同步(如锁)、竞态条件等问题。
二、进程vs线程:资源共享与独占的边界
进程和线程的核心区别在于"资源分配"和"调度"的粒度,明确二者的资源边界是正确使用线程的前提。
2.1 核心区别:资源分配与调度粒度
| 维度 | 进程 | 线程 |
|---|---|---|
| 资源分配 | 资源分配的基本单位 | 调度的基本单位 |
| 地址空间 | 独立地址空间 | 共享进程地址空间 |
| 内核数据结构 | 独立task_struct+mm_struct |
独立task_struct,共享mm_struct |
| 切换成本 | 高(刷新TLB、地址空间) | 低(仅切换上下文) |
| 崩溃影响 | 仅自身崩溃,不影响其他进程 | 整个进程崩溃,所有线程退出 |
2.2 资源共享清单
同一进程的所有线程共享以下资源:
- 虚拟地址空间:代码段(
.text)、数据段(.data/.bss)、堆区、共享库; - 文件相关:文件描述符表、信号处理方式(
SIG_IGN/SIG_DFL/自定义函数); - 进程属性:当前工作目录、用户ID(UID)、组ID(GID)。
2.3 线程独占的"私有财产"
每个线程有自己的私有资源,确保执行独立性:
- 线程ID(内核LWP和用户态
pthread_t); - 执行上下文:寄存器集合、程序计数器(PC);
- 栈空间:主线程栈在栈区,子线程栈在共享区(
mmap区域); - 私有数据:
errno、信号屏蔽字、调度优先级、线程局部存储(TLS)。
三、虚拟地址空间与线程布局:内存视角的线程
要理解线程的内存布局,必须先回顾分页存储管理------线程能共享进程地址空间,本质是通过页表映射到同一块物理内存。
3.1 分页存储:线程共享地址空间的基础
- 虚拟地址通过页表映射到物理内存,进程的
mm_struct存储页目录地址; - 同一进程的线程共享
mm_struct,因此共享虚拟地址→物理地址的映射; - 缺页异常时,内核为进程分配物理页并更新页表,所有线程共享该映射。
简单来说,线程共享"虚拟地址空间的映射关系",因此能访问同一份代码、全局变量和堆内存。
补充:Linux页表结构与地址转换
由于单级页表需连续大内存(4GB)且易产生碎片,所以Linux采用的是两级页表架构。通过页目录管理离散分布的二级页表,仅需 4KB 页目录 + 必要的二级页表内存,大幅节省开销。其虚拟地址映射过程如下(32位平台):
- 选择前10个比特位在页目录当中进行查找,找到对应的页表。
- 再选择中间10个比特位在对应的页表当中进行查找,找到物理内存中对应页框的起始地址。
- 最后将后12个比特位作为偏移量从对应页框的起始地址处向后进行偏移,找到物理内存中某一个对应的字节数据。

3.2 线程在地址空间中的位置
以32位Linux为例,线程在地址空间中的布局如下(从高地址到低地址):
- 内核空间(3GB~4GB):所有线程共享;
- 共享区(mmap区域):子线程栈、共享库、线程局部存储(TLS);
- 堆区:所有线程共享,动态内存分配(
malloc/new)的内存在此; - 数据段(
.data/.bss):所有线程共享,全局变量、静态变量在此; - 代码段(
.text):所有线程共享,程序指令在此; - 主线程栈:仅主线程使用,在栈区(低地址端)。
核心差异:主线程栈在栈区,子线程栈在共享区 ------因为子线程由pthread库创建,库代码加载在共享区,线程栈也随之分配在共享区。
3.3 线程栈与主线程栈的区别
| 特征 | 主线程栈 | 子线程栈 |
|---|---|---|
| 分配位置 | 栈区(低地址端) | 共享区(mmap区域) |
| 大小限制 | 默认8MB,可通过ulimit调整 |
默认8MB,创建时可通过pthread_attr_t指定 |
| 增长方向 | 向下增长(从高地址到低地址) | 向下增长 |
| 生命周期 | 与进程一致 | 与线程一致,线程退出后释放 |
注意:子线程栈不可动态增长,一旦溢出会触发段错误(SIGSEGV);而主线程栈可动态增长(直到栈上限)。
四、线程控制实战:创建、终止、等待与分离
Linux通过POSIX线程库(pthread库)提供线程控制接口,核心函数以pthread_开头,使用时需链接-lpthread选项。
4.1 POSIX线程库:核心说明
- 头文件:
<pthread.h>; - 链接选项:编译时需加
-lpthread(如gcc thread.c -o thread -lpthread); - 错误处理:
pthread函数不设置errno,直接返回错误码,成功返回0。
4.2 线程创建:pthread_create
函数原型:
c
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void*), void *arg);
参数说明:
thread:输出型参数,存储新线程的用户态ID(pthread_t);attr:线程属性(如栈大小、调度优先级),NULL表示默认属性;start_routine:线程启动后执行的函数指针(返回void*,参数void*);arg:传递给start_routine的参数。
示例:创建简单线程
cpp
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
// 线程执行函数
void* thread_func(void* arg) {
const char* msg = (const char*)arg;
while (true) {
cout << "子线程:" << msg << "(线程ID:" << pthread_self() << ")" << endl;
sleep(1);
}
return nullptr;
}
int main() {
pthread_t tid;
// 创建线程
int ret = pthread_create(&tid, NULL, thread_func, (void*)"hello thread");
if (ret != 0) {
cerr << "创建线程失败,错误码:" << ret << endl;
return 1;
}
// 主线程执行
while (true) {
cout << "主线程:运行中(线程ID:" << pthread_self() << ")" << endl;
sleep(1);
}
return 0;
}

4.3 线程终止:三种合法方式
线程终止需避免"暴力退出"(如exit),否则会终止整个进程,合法方式有三种:
- 从线程函数
return:主线程return等同于exit,子线程return仅终止自身; - 调用
pthread_exit:主动终止当前线程,参数为返回值(需指向全局/堆内存); - 其他线程调用
pthread_cancel:取消同一进程的另一个线程。
示例:三种终止方式
cpp
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
using namespace std;
// 方式1:return终止
void* thread_return(void* arg) {
cout << "线程1:通过return终止" << endl;
int* ret = new int(100);
return (void*)ret; // 返回值存储在堆上
}
// 方式2:pthread_exit终止
void* thread_exit(void* arg) {
cout << "线程2:通过pthread_exit终止" << endl;
int* ret = new int(200);
pthread_exit((void*)ret);
}
// 方式3:被pthread_cancel终止
void* thread_cancel(void* arg) {
while (true) {
cout << "线程3:运行中,等待被取消" << endl;
sleep(1);
}
return nullptr;
}
int main() {
pthread_t tid1, tid2, tid3;
void* ret;
// 线程1:return
pthread_create(&tid1, NULL, thread_return, NULL);
pthread_join(tid1, &ret);
cout << "线程1返回值:" << *(int*)ret << endl;
delete ret;
// 线程2:pthread_exit
pthread_create(&tid2, NULL, thread_exit, NULL);
pthread_join(tid2, &ret);
cout << "线程2返回值:" << *(int*)ret << endl;
delete ret;
// 线程3:被取消
pthread_create(&tid3, NULL, thread_cancel, NULL);
sleep(3);
pthread_cancel(tid3);
pthread_join(tid3, &ret);
if (ret == PTHREAD_CANCELED) {
cout << "线程3被成功取消" << endl;
}
return 0;
}

4.4 线程等待:pthread_join的必要性
为什么需要等待?
- 回收线程资源:线程退出后,若未被等待,资源(如线程栈、
task_struct)不会释放,导致资源泄漏; - 获取线程返回值:通过
pthread_join获取线程的终止状态(返回值、是否被取消)。
函数原型:
c
int pthread_join(pthread_t thread, void **value_ptr);
关键说明:
thread:要等待的线程ID(pthread_t);value_ptr:存储线程返回值的指针(若为NULL,表示不关心返回值);- 阻塞特性:调用线程会阻塞,直到目标线程终止。
4.5 线程分离:pthread_detach
若不关心线程返回值,pthread_join会成为负担,此时可通过pthread_detach将线程设置为"分离态"------线程退出后自动释放资源,无需等待。
函数原型:
c
int pthread_detach(pthread_t thread);
使用场景:
- 后台线程(如日志打印、监控),无需主线程等待;
- 避免资源泄漏,简化代码。
示例:线程分离
cpp
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
void* thread_func(void* arg) {
// 线程自分离
pthread_detach(pthread_self());
cout << "分离线程:运行中" << endl;
sleep(2);
cout << "分离线程:退出" << endl;
return nullptr;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
sleep(1); // 等待线程完成分离
// 尝试等待分离线程(会失败)
int ret = pthread_join(tid, NULL);
if (ret != 0) {
cout << "等待分离线程失败:" << ret << endl;
}
sleep(2);
return 0;
}

五、线程ID深度解析:两种ID的本质区别
Linux中有两种线程ID,很多开发者会混淆,需明确二者的作用域和本质:
5.1 内核态线程ID(LWP)
- 定义:内核中标识线程的唯一ID,本质是
pid_t类型(整数); - 作用域:系统级,全系统唯一;
- 查看方式:
ps -aL(LWP列即为内核线程ID); - 关联:每个
task_struct的tid字段存储该ID。
5.2 用户态线程ID(pthread_t)
- 定义:
pthread库提供的线程ID,用于用户态操作线程(如pthread_join); - 本质:在Linux的NPTL实现中,
pthread_t是进程地址空间中的一个地址(指向线程控制块struct pthread); - 作用域:进程级,仅在当前进程内有效;
- 获取方式:
pthread_self()函数。
5.3 查看线程ID
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/syscall.h>
#include <sys/types.h>
// 获取内核线程ID(方法一:使用系统调用)
pid_t get_kernel_thread_id() {
return syscall(SYS_gettid);
}
// 获取内核线程ID(方法二:如果系统支持)
#ifdef __GLIBC__
pid_t gettid() {
return syscall(SYS_gettid);
}
#endif
// 线程函数
void* thread_function(void* arg) {
int thread_num = *(int*)arg;
// 获取两种线程ID
pthread_t pthread_id = pthread_self();
pid_t kernel_tid = get_kernel_thread_id();
pid_t pid = getpid(); // 进程ID
printf("线程 %d:\n", thread_num);
printf(" └─ 进程ID (PID): %d\n", pid);
printf(" └─ 用户级线程ID (pthread_t): %lu\n", (unsigned long)pthread_id);
printf(" └─ 内核级线程ID (TID): %d\n", kernel_tid);
printf(" └─ 主线程的PID和TID是否相同: %s\n",
(pid == kernel_tid) ? "是" : "否");
printf("\n");
sleep(2); // 让线程运行一会儿
return NULL;
}
int main() {
pthread_t threads[3];
int thread_args[3] = {1, 2, 3};
// 先打印主线程的信息
printf("主线程:\n");
printf(" └─ 进程ID (PID): %d\n", getpid());
printf(" └─ 用户级线程ID (pthread_t): %lu\n", (unsigned long)pthread_self());
printf(" └─ 内核级线程ID (TID): %d\n", get_kernel_thread_id());
printf("\n");
// 创建3个子线程
for (int i = 0; i < 3; i++) {
int rc = pthread_create(&threads[i], NULL, thread_function, &thread_args[i]);
if (rc != 0) {
fprintf(stderr, "创建线程 %d 失败\n", i);
exit(1);
}
}
// 等待所有线程完成
for (int i = 0; i < 3; i++) {
pthread_join(threads[i], NULL);
}
return 0;
}

六、线程封装:面向对象的实战实现
pthread库是C语言接口,使用时需手动管理线程生命周期,可通过C++面向对象封装,简化线程操作,支持回调函数、线程命名、分离/joinable模式。
6.1 封装思路
- 核心成员:线程ID(
pthread_t)、线程状态(新建/运行/停止)、回调函数(std::function); - 核心方法:
Start(创建线程)、Join(等待线程)、Detach(分离线程); - 线程函数:静态成员函数作为
pthread_create的入口,内部调用回调函数。
6.2 简单封装类实现(Thread.hpp)
cpp
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>
#include <unistd.h>
namespace ThreadModule {
// 线程状态
enum class ThreadStatus {
NEW, // 新建
RUNNING, // 运行中
STOPPED // 已停止
};
// 线程回调函数类型(支持任意参数,通过std::bind绑定)
using ThreadFunc = std::function<void()>;
class Thread {
public:
// 构造函数:传入回调函数
Thread(ThreadFunc func)
: _func(func), _status(ThreadStatus::NEW), _is_joinable(true) {
// 生成线程名称(Thread-0, Thread-1...)
static uint32_t thread_count = 0;
_name = "Thread-" + std::to_string(thread_count++);
}
// 禁止拷贝构造和赋值
Thread(const Thread&) = delete;
Thread& operator=(const Thread&) = delete;
// 析构函数:确保线程资源释放
~Thread() {
if (_status == ThreadStatus::RUNNING && !_is_joinable) {
pthread_detach(_tid); // 分离态线程自动释放
}
}
// 启动线程
bool Start() {
if (_status != ThreadStatus::NEW) return false;
// 创建线程:静态函数作为入口
int ret = pthread_create(&_tid, nullptr, ThreadEntry, this);
if (ret != 0) {
std::cerr << "创建线程失败,错误码:" << ret << std::endl;
return false;
}
_status = ThreadStatus::RUNNING;
return true;
}
// 等待线程
bool Join() {
if (!_is_joinable || _status != ThreadStatus::RUNNING) return false;
int ret = pthread_join(_tid, nullptr);
if (ret != 0) {
std::cerr << "等待线程失败,错误码:" << ret << std::endl;
return false;
}
_status = ThreadStatus::STOPPED;
return true;
}
// 设置线程为分离态
void SetDetach() {
if (_status == ThreadStatus::NEW) {
_is_joinable = false;
}
}
// 获取线程名称
std::string GetName() const { return _name; }
private:
// 线程入口函数(静态成员函数,无this指针)
static void* ThreadEntry(void* arg) {
Thread* thread = static_cast<Thread*>(arg);
// 设置线程名称(调试用)
pthread_setname_np(pthread_self(), thread->_name.c_str());
// 执行回调函数
thread->_func();
thread->_status = ThreadStatus::STOPPED;
return nullptr;
}
private:
pthread_t _tid; // 线程ID(pthread_t)
std::string _name; // 线程名称
ThreadFunc _func; // 回调函数
ThreadStatus _status; // 线程状态
bool _is_joinable; // 是否可等待(默认true)
};
}
6.3 封装类使用示例
cpp
#include "Thread.hpp"
#include <iostream>
#include <unistd.h>
using namespace ThreadModule;
using namespace std;
// 测试回调函数1:无参数
void TestFunc1() {
while (true) {
cout << "回调函数1:" << pthread_self() << " 运行中" << endl;
sleep(1);
}
}
// 测试回调函数2:有参数(通过std::bind绑定)
void TestFunc2(int num, const string& msg) {
while (true) {
cout << "回调函数2:" << msg << ",编号:" << num << endl;
sleep(1);
}
}
int main() {
// 线程1:无参数回调
Thread t1(TestFunc1);
t1.Start();
// 线程2:有参数回调(std::bind绑定参数)
Thread t2(std::bind(TestFunc2, 100, "Hello Thread"));
t2.SetDetach(); // 设置为分离态
t2.Start();
// 主线程等待线程1
t1.Join();
return 0;
}

可以看到,第一行输出是乱序的,这是因为两个线程的cout操作发生了 "竞态条件";并且我们没法控制输出顺序。这涉及到线程同步与互斥,我们下节讲解。
七、总结与进阶建议
- 本质 :线程是共享进程地址空间的轻量级进程,内核通过
task_struct管理; - 资源边界:共享代码段、堆、文件描述符等,独占栈、寄存器、线程ID等;
- 控制接口 :
pthread库提供创建、终止、等待、分离等接口,需注意错误处理; - 封装思路:通过C++面向对象封装,简化线程管理,支持灵活的回调函数;
- 避坑点:线程崩溃导致进程退出、线程栈溢出、资源泄漏(未等待/分离)。