POSIX 线程库(简称 pthread)是 UNIX/Linux 系统下遵循 POSIX 标准 的多线程编程接口,提供了一套跨平台的线程创建、同步和管理机制。
1. 线程创建
1.1 pthread_create
pthread_create()是 POSIX 线程库中用于创建新线程的核心函数,其功能类似于进程中的fork(),但更轻量级
返回值:成功时返回 0,失败时返回错误码(非 errno,需用 strerror() 转换)。
参数解析
参数 | 类型 | 作用 |
---|---|---|
thread | pthread_t * | 输出参数,用于保存新线程的唯一标识符(线程ID)。 |
attr | pthread_attr_t * | 线程属性(如栈大小、调度策略),传 NULL 表示使用默认属性。 |
start_routine | void *(*)(void *) | 线程入口函数,格式必须为 void *func(void *args) 。 |
arg | void * | 传递给 start_routine 的参数,需强制转换为 void* 类型。 |
线程被创建好后,新线程要被主线程等待,类似僵尸进程的问题。
1.2 pthread_join
pthread_join 是 POSIX 线程库中用于线程同步和资源回收的关键函数,主要作用是阻塞当前线程,直到目标线程终止,并获取其返回值。
pthread_t thread, // 目标线程的ID(需等待的线程)
void **retval // 输出参数:存储目标线程的返回值(可设为NULL)
成功返回 0
,失败返回错误码(非 errno
)。
#include <iostream>
#include <string>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>
void showtid(pthread_t tid)
{
printf("tid: 0x%lx\n", tid);
}
void *routine(void *args)
{
std::string name = static_cast<const char*>(args);
int cnt = 5;
while(cnt--)
{
std::cout << "我是一个新线程:" << name << std::endl;
}
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, routine, (void*)"thread-1");
showtid(tid);
pthread_join(tid, nullptr);
return 0;
}
1.3 pthread_self
pthread_self()是 POSIX 线程库中用于获取当前线程唯一标识符(线程ID)的函数,其功能类似于进程中的 getpid()。
#include <iostream>
#include <string>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>
int flag = 1000;
void showtid(pthread_t tid)
{
printf("tid: 0x%lx\n", tid);
}
std::string FormatId(pthread_t id)
{
char tid[64];
snprintf(tid, sizeof(tid), "0x%lx", id);
return tid;
}
void *routine(void *args)
{
std::string name = static_cast<const char*>(args);
pthread_t id = pthread_self();
int cnt = 5;
while(cnt--)
{
sleep(1);
std::cout << "我是一个新线程:" << name << "我的id'是:" << FormatId(id) << std::endl;
flag++;
}
return (void*)100;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, routine, (void*)"thread-1");
showtid(tid);
int cnt = 5;
while(cnt--)
{
std::cout << "我是主线程:" << "我的id'是:"
<< FormatId(pthread_self()) << ", flag: " << flag << std::endl;
sleep(1);
}
void *ret = nullptr;
pthread_join(tid, &ret);
std::cout << "ret: " << (long long)ret << std::endl;
return 0;
}
可以看到两个线程共享同一份资源
两个线程都调用了这个函数,这是一个可重入函数
std::string FormatId(pthread_t id)
{
char tid[64];
snprintf(tid, sizeof(tid), "0x%lx", id);
return tid;
}
给新线程传递的参数和返回值可以是任意类型
class Task
{
public:
Task(int a, int b)
: _a(a)
, _b(b)
{}
int Execute()
{
return _a + _b;
}
~Task(){}
private:
int _a;
int _b;
};
class Result
{
public:
Result(int result) : _result(result){}
int GetResult() { return _result;}
~Result(){}
private:
int _result;
};
void *routinue(void *args)
{
Task *t = static_cast<Task*>(args);
sleep(1);
Result *res = new Result(t->Execute());
sleep(1);
return res;
}
int main()
{
pthread_t tid;
Task *t = new Task(10, 20);
pthread_create(&tid, nullptr, routinue, (void*)t);
Result *ret = nullptr;
pthread_join(tid, (void **)&ret);
int n = ret->GetResult();
std::cout << "进程结束,退出码:" << n << std::endl;
delete t;
delete ret;
return 0;
}
2. 线程终止
1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。

3. ⼀个线程可以调用pthread_ cancel终止同⼀进程中的另⼀个线程。
pthread_cancel用来取消同一个进程中的线程
返回值:成功返回0,失败返回错误码
#include <iostream>
#include <string>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>
class Task
{
public:
Task(int a, int b)
: _a(a)
, _b(b)
{}
int Execute()
{
return _a + _b;
}
~Task(){}
private:
int _a;
int _b;
};
class Result
{
public:
Result(int result) : _result(result){}
int GetResult() { return _result;}
~Result(){}
private:
int _result;
};
void *routinue(void *args)
{
Task *t = static_cast<Task*>(args);
sleep(100);
Result *res = new Result(t->Execute());
//return res;
pthread_exit(res); //与return res等价
}
int main()
{
pthread_t tid;
Task *t = new Task(10, 20);
pthread_create(&tid, nullptr, routinue, (void*)t);
sleep(3);
pthread_cancel(tid);
std::cout << "新线程被取消" << std::endl;
void *ret = nullptr;
pthread_join(tid, &ret);
std::cout << "新线程结束,退出码:" << (long long)ret << std::endl;
return 0;
}
线程如果被退出,退出结果是-1
thread 线程以不同的方式终止,通过 pthread_join 得到的终止状态是不同的,总结如下:
1. 如果 thread 线程通过 return 返回,value_ptr 所指向的单元里存放的是 thread 线程函数的返回值。
2. 如果 thread 线程被别的线程调用 pthread_cancel 异常终止,value_ptr 所指向的单元里存放的是常数 PTHREAD_CANCELED。
3. 如果 thread 线程是自己调用 pthread_exit 终止的,value_ptr 所指向的单元存放的是传给 pthread_exit 的参数。
4. 如果对 thread 线程的终止状态不感兴趣,可以传 NULL 给 value_ptr 参数。
3.分离线程
默认情况下,新创建的线程是 joinable 的,线程退出后,需要对其进行 pthread_join 操作,否则,
无法释放资源,从而造成系统泄漏。 如果不关心线程的返回值,join是⼀种负担,这个时候,我们
将线程设置为分离状态,当线程退出时,自动释放线程资源。
pthread_detach用于将线程设置为 分离状态(detached),使得线程结束时自动释放资源,而无需调用 pthread_jion。

void *routinue(void *args)
{
pthread_detach(pthread_self());
std::cout << "线程被分离" << std::endl;
int cnt = 5;
while(cnt--)
{
sleep(1);
std::cout << "我是新进程" << std::endl;
}
return nullptr;
}
int main()
{
pthread_t tid;
Task *t = new Task(10, 20);
pthread_create(&tid, nullptr, routinue, (void*)t);
int cnt = 5;
while(cnt--)
{
sleep(1);
std::cout << "我是主进程" << std::endl;
}
int n = pthread_join(tid, nullptr);
if(n == 0)
{
std::cout << "pthread_join sucess: " << std::endl;
}else
{
std::cout << "pthread_join fail: " << std::endl;
}
return 0;
}

4. 用户态线程(pthread)与 struct pthread(TCB)
- struct pthread(TCB,Thread Control Block)
- 是 pthread 库在 用户态 维护的线程管理结构,包含:
struct pthread {
void* ret; // 线程返回值(通过 return 或 pthread_exit 写入)
void* stack; // 线程独立栈的地址
size_t stack_size; // 栈大小
pthread_attr_t attr; // 线程属性(如分离状态、调度策略)
pid_t tid; // 内核 LWP(通过 gettid() 获取)
// 其他状态(如取消标志、锁等)
};
- pthread_create 会动态分配 struct pthread 对象,并返回其地址(即 pthread_t,本质是用户态句柄)。使用pthread_create,会在库中创建TCB,在内核中创建轻量级进程(调用系统调用clone)
- 线程退出时
-struct pthread 也就是TCB,有一个属性void *(ret),当线程执行return或调用 `pthread_exit()时,就会把返回值写入ret中。
- 此时线程虽然执行结束,但这个数据块并没有被释放,所以需要pthread_join,并且需要传入该线程的tid作为参数已找到该线程对应的数据块
5. 内核态线程(LWP)与 task_struct(PCB)
- clone() 系统调用
- pthread_create 底层通过 clone() 创建 内核线程(LWP),关键参数:
clone(
fn, // 线程函数
stack, // 用户态栈地址
CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD,
args // 传递给线程的参数
);
- `CLONE_THREAD`:新线程共享相同的 `PID`(属于同一线程组)。
- `CLONE_VM`:共享地址空间(同一进程)。
- task_struct(PCB)
- 内核为每个 LWP 维护一个 `task_struct`,包含:
struct task_struct {
pid_t pid; // 线程组 ID(用户态看到的 PID)
pid_t tgid; // 等同于 pid(主线程的 PID)
pid_t tid; // 内核 LWP(唯一线程 ID)
struct mm_struct *mm; // 内存管理(共享同一进程的 mm)
// 调度相关:时间片、优先级、上下文(registers、FPU state)
};
用户态与内核态的协作
- pthread_t vs LWP
| 概念 | 用户态(pthread 库) | 内核态(Linux 内核) |
|--------------------|--------------------------------------|-----------------------------------------|
| 线程标识符 | pthread_t(TCB 地址) | tid(LWP,通过 `gettid()` 获取) |
| 调度单位 | 无(依赖内核 LWP) | task_struct(LWP) |
| *栈管理 | 用户态分配栈(`stack` 属性) | 内核映射用户栈到进程地址空间 |
- 线程创建流程
1. 用户调用 pthread_create。
2. pthread 库分配 struct pthread(TCB)和用户栈。
3. 调用 clone() 创建内核线程(LWP),共享进程地址空间。
4. 新线程从用户态启动函数(fn)开始执行。
- 线程退出流程
1. 线程调用 return 或 pthread_exit,返回值写入 TCB 的 ret。
2. 内核线程(LWP)退出,但 struct pthread 仍保留(供 `pthread_join` 读取)。
3. pthread_join 回收 TCB 和用户栈资源。
pthread_t 到底是什么?
- 在 glibc 中,pthread_t 是 struct pthread(用户态 TCB 的地址)。
- 其他实现(如 musl libc)可能直接使用 `tid`(LWP),但 Linux 主流实现是 TCB 地址。
总结
- 用户态 pthread 库 管理 struct pthread(TCB),负责线程创建、属性、返回值等。
- 内核态 task_struct 管理 LWP,负责调度、时间片、上下文切换。
- pthread_t 是用户态句柄,LWP 是内核调度单位,二者通过 clone() 协作。
- 务必调用 `pthread_join 或 pthread_detach 避免资源泄漏!
6.独立栈
1. 主线程(进程)的栈
- 分配方式:
- 通过 `fork()` 创建时,继承父进程的栈空间地址,采用 写时拷贝(CoW)机制。
- 栈空间可动态增长(由内核自动扩展),直至达到上限(`ulimit -s` 设置的值,默认通常 8MB)。
- 溢出行为:
- 访问未映射的栈地址时,内核尝试扩展栈;若超出上限则触发 段错误(SIGSEGV)。
- 特点:
- 向下增长:栈指针从高地址向低地址移动。
- 唯一允许“试探性”访问未映射页而不立即报错的内存区域(直到触及硬限制)。
2. 子线程(pthread)的栈
- 分配方式:
- 通过 `pthread_create()` 创建时,由 glibc 调用 `mmap` 在进程的 文件映射区(共享区)分配固定大小的栈空间。
mem = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
- 默认大小通常为 8MB,可通过 `pthread_attr_setstacksize()` 自定义。
- 溢出行为:
- 不可动态增长:栈耗尽时直接访问非法内存,立即触发段错误。
- 特点:
- 固定大小:由 `mmap` 预先分配,无 CoW 机制。
- 非严格私有:虽为线程私有,但因共享进程地址空间,其他线程可通过指针非法访问(需同步控制)。独立的栈,是指其他线程不知道该栈的地址,但是可以访问里面的内容,因为共享地址空间。
线程独立的上下文:有独立的PCB(内核)+ TCB(用户层,pthread库内部)
独立的栈:每个线程线程都有自己独立的栈,要么是进程的,要么是库中创建进程是mmap申请出来的