POSIX线程(通常称为pthreads)是IEEE制定的操作系统线程API标准。Linux系统通过glibc库实现了这个标准,提供了创建和管理线程的一系列函数。
核心特性
pthread_ 开头,这使得代码中线程相关的操作非常清晰易辨。
#include <pthread.h> 来包含所有数据类型和函数的声明。
-lpthread 或 -pthread 链接选项来链接线程库。
-pthread 通常是更推荐的选择,因为它除了链接库之外,还可能定义必要的预处理宏,确保代码的可移植性。
为什么需要 -lpthread 选项?
这是一个非常重要的实践点。C语言的标准库(libc)默认不包含pthread函数的具体实现。这些实现存在于一个独立的共享库文件中,通常是 libpthread.so。
-l 选项告诉链接器(ld)要去链接一个库。
pthread 是 libpthread.so 的简写(链接器会自动添加 lib 前缀和 .so 后缀)。
因此,-lpthread 的本质是:“链接器,请将我们的程序与名为 libpthread.so 的共享库链接起来,以便解析所有以 pthread_ 开头的函数。”
#include <pthread.h>
int pthread_create(
pthread_t *thread, // 线程标识符(输出参数)
const pthread_attr_t *attr, // 线程属性(可为NULL)
void *(*start_routine)(void *), // 线程入口函数指针
void *arg // 入口函数的参数
);1. pthread_t *thread - 线程标识符
pthread_t 是一个不透明的数据类型,通常是一个整数或结构体指针,具体实现取决于系统(Linux中为unsigned long,macOS中为结构体)。
pthread_t 是整数类型,如果需要比较线程ID,应使用 pthread_equal() 函数。获取当前线程ID使用 pthread_self()。
2. const pthread_attr_t *attr - 线程属性
NULL,则使用默认属性。
3. void *(*start_routine)(void*) - 线程函数
void* 参数并返回一个 void* 值。
start_routine 函数的开始处执行,直到:
pthread_exit()(线程显式终止)
pthread_cancel())
pthread_join() 获取。
4. void *arg - 线程参数
void* 类型,可以传递任何数据类型的地址。
errno
EAGAIN:系统资源不足,无法创建线程,或已超过线程数量限制
EINVAL:attr 参数无效
EPERM:没有权限设置指定的调度策略或参数
示例:
#include <iostream>
#include <string>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
void *routine(void *arg)
{
std::string name = static_cast<const char*>(arg);
int cnt = 5;
while(cnt--)
{
std::cout << "我是一个新线程: " << name << ", pid: " << getpid() << std::endl;
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, routine, (void *)"thread-1");
while (true)
{
std::cout << "main主线程, pid: " << getpid() << std::endl;
sleep(1);
}
return 0;
}运行结果:
ltx@iv-ye1i2elts0wh2yp1ahah:~/gitLinux/Linux_system/lesson_thread/ThreadControl$ ./test
main主线程, pid: 221797
我是一个新线程: thread-1, pid: 221797
main主线程, pid: 我是一个新线程: thread-1221797, pid:
221797
main主线程, pid: 221797
我是一个新线程: thread-1, pid: 221797
main主线程, pid: 221797
我是一个新线程: thread-1, pid: 221797
main主线程, pid: 221797
我是一个新线程: thread-1, pid: 221797
main主线程, pid: 221797
main主线程, pid: 221797
main主线程, pid: 221797
main主线程, pid: 221797
main主线程, pid: 221797
main主线程, pid: 221797注意:创建新线程后,两个线程同时向显示屏输出,会造成数据竞争,导致打印的输出信息混在了一起
通过 ps -aL 指令可以查看,-L 选项:打印线程信息
ltx@iv-ye1i2elts0wh2yp1ahah:~/gitLinux/Linux_system/lesson_thread/ThreadControl$ while :; do ps -aL | head -1 && ps -aL | grep test ; sleep 1 ; done
PID LWP TTY TIME CMD
PID LWP TTY TIME CMD
PID LWP TTY TIME CMD
221797 221797 pts/4 00:00:00 test
221797 221798 pts/4 00:00:00 test
PID LWP TTY TIME CMD
221797 221797 pts/4 00:00:00 test
221797 221798 pts/4 00:00:00 test
PID LWP TTY TIME CMD
221797 221797 pts/4 00:00:00 test
221797 221798 pts/4 00:00:00 test
PID LWP TTY TIME CMD
221797 221797 pts/4 00:00:00 test
221797 221798 pts/4 00:00:00 test
PID LWP TTY TIME CMD
221797 221797 pts/4 00:00:00 test
221797 221798 pts/4 00:00:00 test
PID LWP TTY TIME CMD
221797 221797 pts/4 00:00:00 test
PID LWP TTY TIME CMD
221797 221797 pts/4 00:00:00 test
PID LWP TTY TIME CMD
221797 221797 pts/4 00:00:00 test
PID LWP TTY TIME CMD
221797 221797 pts/4 00:00:00 test
PID LWP TTY TIME CMD
221797 221797 pts/4 00:00:00 test
PID LWP TTY TIME CMD
PID LWP TTY TIME CMD
PID LWP TTY TIME CMDPID (进程ID): 两个线程都有相同的PID (221797)。这证明了它们属于同一个进程。
LWP (轻量级进程ID): 每个线程有不同的LWP (221797 和 221798)。
可以直观感受到,线程本质上就是共享相同地址空间和其他资源的"轻量级进程"。
那tid是啥呢?我们也可以将tid打印出来看一下,通过 pthread 库中函数 pthread_self 的返回值得到
函数原型
#include <pthread.h>
pthread_t pthread_self(void);pthread_t 类型,表示当前线程的唯一标识符线程 ID (pthread_t) 的本质
unsigned long(Linux 实现)示例:
#include <iostream>
#include <string>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
void showid()
{
printf("tid: %lu\n", pthread_self());
}
void *routine(void *arg)
{
std::string name = static_cast<const char*>(arg);
int cnt = 5;
while(cnt--)
{
std::cout << "我是一个新线程: " << name << ", pid: " << getpid() << std::endl;
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, routine, (void *)"thread-1");
showid();
while (true)
{
std::cout << "main主线程, pid: " << getpid() << std::endl;
sleep(1);
}
return 0;
}运行结果:

在Linux系统中,实际上存在两种不同意义上的“线程ID”,它们处于不同的抽象层次,有不同的用途。
pthread_t (POSIX线程ID) - 用户态/库级别IDpthread_join, pthread_cancel等)。内核完全不知道这个ID的存在。
pthread_t(结构体、整数等)。
pthread_equal()来比较,不要直接用==(为了可移植性)。
pthread_self()获取。
pid_t类型的整数,与进程PID属于同一种类型。内核为每一个调度实体(无论是进程还是线程)都分配一个唯一的ID。
top, ps, perf等工具看到和使用的就是这个ID。
gettid()来获取。
“pthread_self 得到的这个数实际上是一个地址”
“LWP 得到的是真正的线程ID”
从内核视角看,LWP(由gettid()返回)才是线程的“真实身份”,是调度和资源分配的基本单位。
“主线程和其他线程的栈位置”
对栈位置的描述是Linux线程实现的另一个关键点!
ulimit -s查看)。
pthread_create可以指定栈大小的原因。

这种设计体现了优秀的抽象分层思想:
pthread_t,不关心底层实现。应用程序使用pthread_t可以保证在不同UNIX系统之间的可移植性。
因此:
pthread_self()得到的ID是给pthread库用的,用于进程内线程管理。
gettid()或ps -L看到的LWP是给内核用的,用于系统级任务调度。

那既然在内核中,由库来实现和管理线程,那要如何管理起来呢?先描述再组织
pthreads库虽然运行在用户空间,但它通过精巧的数据结构设计和系统调用封装,实现了完整的线程管理功能。
pthreads库为每个线程创建一个线程控制块(Thread Control Block, TCB) 数据结构,这就是对线程的"描述"。TCB包含了管理一个线程所需的全部信息:
// 简化的TCB结构示意(实际实现更复杂)
struct pthread {
/* 线程标识和状态 */
pthread_t thread_id; // 线程ID(通常是TCB自身的地址)
int detach_state; // 分离状态
int cancel_state; // 取消状态
int cancel_type; // 取消类型
/* 线程上下文 */
void *stack_base; // 栈基地址
size_t stack_size; // 栈大小
void *(*start_routine)(void*); // 线程函数
void *arg; // 线程参数
void *return_value; // 返回值
/* 寄存器上下文(用于切换时保存/恢复) */
void *machine_context; // 平台相关的寄存器保存区
/* 同步和信号处理 */
// 各种互斥锁、条件变量、信号处理信息
/* 链接信息 */
struct pthread *prev, *next; // 用于组织到线程列表中
};每个TCB就是线程的"身份证"和"档案",完整描述了线程的所有属性和状态。
pthread库通过以下数据结构组织所有线程的TCB,实现快速访问与调度:
struct pthread *__thread_list[MAX_THREADS]pthread_t)作为下标直接定位TCB(#ref1)TCB = __thread_list[(unsigned long)pthread_self % MAX_THREADS]hash_map<pid_t LWP, struct pthread* TCB>虽然pthreads库在用户空间管理线程,但它需要内核的支持来实现真正的并发执行:
pthread_create()时:
clone()系统调用,请求内核创建真正的执行上下文
线程退出和清理
当线程结束时,pthreads库需要:
pthread库的线程管理是用户态与内核态协作的典范:
pthread_t 作为TCB指针提供进程内唯一标识clone/futex 等系统调用这是最自然、最推荐的线程终止方式。
工作原理:
return 语句时,线程会正常结束
pthread_join 获取
注意事项:
main 函数 return 会终止整个进程
这种方式允许线程在任何地方主动终止自己,而不必返回到函数开头。
函数原型:
void pthread_exit(void *value_ptr);使用场景:
重要注意事项:
value_ptr 不能指向线程栈上的局部变量,因为线程退出后栈会被销毁
pthread_exit 会终止主线程,但其他线程会继续运行,直到所有线程都结束
pthread_exit 会执行线程的清理处理程序(通过 pthread_cleanup_push 注册的)
这种方式允许一个线程请求终止同一进程中的另一个线程。
函数原型:
int pthread_cancel(pthread_t thread);取消机制的工作原理: 线程取消不是立即发生的,而是依赖于目标线程的取消状态和类型:
pthread_setcancelstate 设置):
PTHREAD_CANCEL_ENABLE:允许取消(默认)
PTHREAD_CANCEL_DISABLE:禁止取消
pthread_setcanceltype 设置):
PTHREAD_CANCEL_DEFERRED:延迟取消(默认),只在取消点检查取消请求
PTHREAD_CANCEL_ASYNCHRONOUS:异步取消,可以在任何时间点被取消
取消点:一些特定的函数调用会成为取消点,如:
sleep(), usleep(), nanosleep()
read(), write(), open(), close()
pthread_join(), pthread_cond_wait()
return 还是 pthread_exit 返回的值,都必须指向全局数据或堆上分配的内存
pthread_cleanup_push 和 pthread_cleanup_pop 注册清理函数
pthread_join 来回收资源
pthread_join 是获取线程返回值的标准机制
int pthread_join(pthread_t thread, void **value_ptr);pthread_t 标识符(由 pthread_create 返回)NULL,表示忽略退出状态NULL 时,*value_ptr 存储退出信息指针value_ptr接收的值取决于线程终止方式,形成状态三元组:
终止方式 | value_ptr指向的内容 | 典型场景 |
|---|---|---|
return 退出 | 线程函数返回值 | return (void*)42; |
pthread_exit() | pthread_exit 的参数值 | pthread_exit((void*)"done") |
pthread_cancel() 取消 | PTHREAD_CANCELED 宏(-1) | pthread_cancel(tid) |
📌 关键细节:
PTHREAD_CANCELED 实际为 (void*)-1,需强转 int 判断return 和 pthread_exit 返回的值必须位于全局内存或堆中(禁止指向栈变量)综合示例:
#include <iostream>
#include <string>
#include <cstdlib>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
void *thread1(void *arg)
{
printf("thread 1 returning ... \n");
int *p = (int *)malloc(sizeof(int));
*p = 1;
return (void *)p;
}
void *thread2(void *arg)
{
printf("thread 2 exiting ...\n");
int *p = (int *)malloc(sizeof(int));
*p = 2;
pthread_exit((void *)p);
}
void *thread3(void *arg)
{
while (true)
{
printf("thread 3 is running ...\n");
sleep(1);
}
return NULL;
}
int main()
{
pthread_t tid;
void *ret;
// thread 1 return
pthread_create(&tid, NULL, thread1, NULL);
pthread_join(tid, &ret);
printf("thread return, thread id %lX, return code:%d\n", tid, *(int *)ret);
free(ret);
// thread 2 exit
pthread_create(&tid, NULL, thread2, NULL);
pthread_join(tid, &ret);
printf("thread return, thread id %lX, return code:%d\n", tid, *(int *)ret);
free(ret);
// thread 3 cancel by other
pthread_create(&tid, NULL, thread3, NULL);
sleep(3);
pthread_cancel(tid);
pthread_join(tid, &ret);
if (ret == PTHREAD_CANCELED)
printf("thread return, thread id %lX, return code:PTHREAD_CANCELED\n", tid);
else
printf("thread return, thread id %lX, return code:NULL\n", tid);
}运行结果:


pthread_join
pthread_join 会失败并返回 EINVAL
pthread_join 获取的返回值内存必须由调用者负责释放
pthread_join 的返回值
ESRCH:没有找到与给定线程ID对应的线程
EINVAL:线程不是可连接状态,或者另一个线程已经在等待此线程
EDEADLK:死锁情况,例如线程尝试join自己
pthread_join 没有超时机制,会无限期等待
默认情况:可连接线程(Joinable Thread)
pthread_join 来回收资源
分离线程(Detached Thread)
int pthread_detach(pthread_t thread);参数说明
thread:要分离的线程ID
返回值
1. 创建时分离(推荐)
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 设置分离属性
pthread_create(&tid, &attr, worker, NULL); // 直接创建分离线程
pthread_attr_destroy(&attr);2. 其他线程分离目标线程
pthread_create(&tid, NULL, worker, NULL);
pthread_detach(tid); // 主线程主动分离子线程3. 线程自我分离
void* worker(void* arg) {
pthread_detach(pthread_self()); // 线程内自行分离
// ... 业务逻辑
return NULL;
}示例:
void *thread_run(void *arg)
{
pthread_detach(pthread_self());
printf("%s\n", (char *)arg);
return NULL;
}
int main(void)
{
pthread_t tid;
if (pthread_create(&tid, NULL, thread_run, (void*)"thread1 run...") != 0)
{
printf("create thread error\n");
return 1;
}
int ret = 0;
sleep(1); // 很重要,要让线程先分离,再等待
if (pthread_join(tid, NULL) == 0)
{
printf("pthread wait success\n");
ret = 0;
}
else
{
printf("pthread wait failed\n");
ret = 1;
}
return ret;
}运行结果:

1. Joinable 和 Detached 是互斥的
2. 分离时机
3. 资源回收
4. 错误处理
总是检查 pthread_detach 的返回值:
int result = pthread_detach(thread);
if (result != 0) {
// 处理错误
if (result == EINVAL) {
fprintf(stderr, "Thread is already detached or doesn't exist\n");
} else if (result == ESRCH) {
fprintf(stderr, "No thread with the ID could be found\n");
}
}线程分离是多线程编程中的重要概念,它提供了自动资源回收的机制:
pthread_detach(thread_id)
pthread_detach(pthread_self())
pthread_join
代码如下:
Thread.hpp:
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include <cstdio>
#include <cstring>
#include <functional>
namespace ThreadModlue
{
static uint32_t number = 1; // 不是原子型的,先不处理
class Thread
{
using func_t = std::function<void()>;
private:
void Enabledetach()
{
std::cout << "线程被分离了" << std::endl;
_isdetach = true;
}
void EnableRunning()
{
_isrunning = true;
}
// 新线程执行
static void* Routine(void* args) // 属于类内的成员函数,默认包含this指针!
{
Thread* self = static_cast<Thread*>(args);
self->EnableRunning(); // 修改运行标志位
if(self->_isdetach)
{
self->Detach();
}
pthread_setname_np(self->_tid, self->_name.c_str());
self->_func(); // 回调处理
return nullptr;
}
public:
Thread(func_t func)
: _tid(0), _isdetach(false), _isrunning(false), _ret(nullptr), _func(func)
{
_name = "thread-" + std::to_string(number);
}
void Detach()
{
if (_isdetach)
return;
if (_isrunning)
pthread_detach(_tid);
Enabledetach();
}
bool Start()
{
if (_isrunning)
return false;
int n = pthread_create(&_tid, nullptr, Routine, this); // 传this指针
if (n != 0)
{
std::cerr << "create thread error : " << strerror(n) << std::endl;
return false;
}
else
{
std::cout << "create thread success" << std::endl;
return true;
}
}
bool Stop()
{
if (_isrunning)
{
int n = pthread_cancel(_tid);
if (n != 0)
{
std::cerr << "cancel thread error : " << strerror(n) << std::endl;
return false;
}
else
{
std::cout << _name << " stop!" << std::endl;
return true;
}
}
return false;
}
void Join()
{
if(_isdetach)
{
std::cout << "你的线程已经被分离了, 不能join" << std::endl;
}
int n = pthread_join(_tid, &_ret);
if(n != 0)
{
std::cerr << "pthread_join error : " << strerror(n) << std::endl;
return;
}
else
{
std::cout << "join success" << std::endl;
}
}
~Thread() {}
private:
pthread_t _tid;
std::string _name;
bool _isdetach; // 分离标志位
bool _isrunning; // 运行标志位
void* _ret;
func_t _func;
};
}Main.cc:
#include "Thread.hpp"
#include <unistd.h>
using namespace ThreadModlue;
int main()
{
Thread t([](){
while(true)
{
char name[128];
pthread_getname_np(pthread_self(), name, sizeof(name));
std::cout << "我是一个新线程: " << name << std::endl;
sleep(1);
}
});
t.Start();
t.Detach();
sleep(5);
t.Stop();
sleep(5);
t.Join();
return 0;
}运行结果:

注意:
pthread_setname_np 和 pthread_getname_np 是两个用于管理线程名称的非标准函数("_np"后缀表示"non-portable",即不可移植)。这些函数主要适用于Linux和其他类Unix系统,常用于多线程程序的调试与管理。