1. POSIX线程库 (pthreads)
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_
开头的函数。"
2. 线程创建
函数原型与核心机制
cpp
#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
,则使用默认属性。 -
可配置属性包括:
-
分离状态(detached state)
-
调度策略和参数(scheduling policy and parameters)
-
栈大小(stack size)
-
栈地址(stack address)
-
守卫区大小(guard size)
-
线程的竞争范围(contention scope)
-
3. void *(*start_routine)(void*)
- 线程函数
-
形式 :线程函数必须符合特定的签名 - 接受一个
void*
参数并返回一个void*
值。 -
执行流程 :新线程从
start_routine
函数的开始处执行,直到:-
函数返回(线程隐式终止)
-
调用
pthread_exit()
(线程显式终止) -
被其他线程取消(
pthread_cancel()
)
-
-
返回值 :线程函数的返回值可以通过
pthread_join()
获取。
4. void *arg
- 线程参数
-
用途:传递给线程函数的参数。
-
灵活性 :由于是
void*
类型,可以传递任何数据类型的地址。 -
注意事项:
-
确保参数在线程使用期间保持有效
-
如果传递栈上变量的地址,要确保原函数不会在线程使用前返回
-
通常使用动态分配的内存或全局变量传递数据
-
返回值与错误处理
-
成功:返回 0
-
失败 :返回错误码(非零值),不设置
errno
-
常见错误码:
-
EAGAIN
:系统资源不足,无法创建线程,或已超过线程数量限制 -
EINVAL
:attr
参数无效 -
EPERM
:没有权限设置指定的调度策略或参数
-
示例:
cpp
#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;
}
运行结果:
bash
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 选项:打印线程信息
bash
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 CMD
PID (进程ID) : 两个线程都有相同的PID (221797)。这证明了它们属于同一个进程。
LWP (轻量级进程ID) : 每个线程有不同的LWP (221797 和 221798)。
-
LWP是线程在内核中的唯一标识符。
-
主线程的LWP通常等于PID。
-
其他线程有自己唯一的LWP。
可以直观感受到,线程本质上就是共享相同地址空间和其他资源的"轻量级进程"。
那tid是啥呢?我们也可以将tid打印出来看一下,通过 pthread 库中函数 pthread_self 的返回值得到
pthread_self - 获取当前线程ID
函数原型
cpp
#include <pthread.h>
pthread_t pthread_self(void);
- 参数:无
- 返回值 :
pthread_t
类型,表示当前线程的唯一标识符 - 错误码:永不失败(总是成功)
线程 ID (pthread_t
) 的本质
- 数据类型 :
- 通常为
unsigned long
(Linux 实现) - 具体类型由操作系统实现定义,可能是整型或结构体
- 通常为
- 生命周期 :
- 正在运行的线程 ID 唯一
- 终止后 ID 可被新线程复用(非永久唯一)
- 作用域:仅在同一进程内有效,跨进程无意义
示例:
cpp
#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;
}
运行结果:

深入理解两种"线程ID"
在Linux系统中,实际上存在两种不同意义上的"线程ID",它们处于不同的抽象层次,有不同的用途。
1. pthread_t
(POSIX线程ID) - 用户态/库级别ID
-
来源 :由pthread线程库分配和管理。
-
本质 :在Linux的glibc实现中,它确实是一个内存地址 。具体来说,它是该线程的线程控制结构(TCB, Thread Control Block)在进程地址空间中的地址。
-
作用域 :进程内有效 。它只在当前进程内有意义,用于在pthread库的函数中标识线程(如
pthread_join
,pthread_cancel
等)。内核完全不知道这个ID的存在。 -
用途 :用于同一进程内的线程间操作和同步。
-
特点:
-
可移植性差。不同操作系统或不同libc实现可能用不同的方式表示
pthread_t
(结构体、整数等)。 -
使用
pthread_equal()
来比较,不要直接用==
(为了可移植性)。 -
使用
pthread_self()
获取。
-
2. LWP (Light Weight Process ID) / TID (Thread ID) - 内核态/系统级别ID
-
来源 :由Linux内核分配和管理。
-
本质 :这是一个
pid_t
类型的整数,与进程PID属于同一种类型。内核为每一个调度实体(无论是进程还是线程)都分配一个唯一的ID。 -
作用域 :系统全局有效。在整个操作系统范围内唯一标识一个调度任务。
-
用途 :用于系统级 的监控、调度和调试。
top
,ps
,perf
等工具看到和使用的就是这个ID。 -
特点:
-
在Linux中,可以通过系统调用
gettid()
来获取。 -
主线程的LWP等于进程的PID。
-
其他线程的LWP是内核分配的新ID。
-
关键点详解
"pthread_self 得到的这个数实际上是一个地址"
"LWP 得到的是真正的线程ID"
从内核视角看,LWP(由gettid()
返回)才是线程的"真实身份",是调度和资源分配的基本单位。
"主线程和其他线程的栈位置"
对栈位置的描述是Linux线程实现的另一个关键点!
-
主线程的栈 :位于进程虚拟地址空间的栈区域 。这个栈是在程序启动时由内核自动设置的,大小通常由系统限制决定(可以用
ulimit -s
查看)。 -
其他线程的栈 :由pthread库 在进程的堆和栈之间的共享区域 中动态分配 。这就是为什么
pthread_create
可以指定栈大小的原因。-
线程栈的分配和管理是pthread库的职责。
-
当线程退出时,pthread库负责回收这片栈内存。
-

为什么要设计两层ID?
这种设计体现了优秀的抽象分层思想:
-
可移植性 :POSIX标准只定义了
pthread_t
,不关心底层实现。应用程序使用pthread_t
可以保证在不同UNIX系统之间的可移植性。 -
灵活性:pthread库可以自由选择如何实现和管理线程,比如将TCB结构体放在堆上,并用其地址作为ID。
-
效率:用户态的线程操作(如获取自身ID)非常快,无需陷入内核。
-
内核简洁性:内核不需要理解复杂的线程库数据结构,它只需要管理好轻量级进程(LWP)的调度即可。
因此:
-
用
pthread_self()
得到的ID是给pthread库用的,用于进程内线程管理。 -
用
gettid()
或ps -L
看到的LWP是给内核用的,用于系统级任务调度。 -
两者各司其职,共同构成了Linux强大而灵活的多线程能力。

那既然在内核中,由库来实现和管理线程,那要如何管理起来呢?先描述再组织
pthreads库如何"先描述,再组织"地管理线程
pthreads库虽然运行在用户空间,但它通过精巧的数据结构设计和系统调用封装,实现了完整的线程管理功能。
1. "先描述" - 定义线程控制块(TCB)
pthreads库为每个线程创建一个线程控制块(Thread Control Block, TCB) 数据结构,这就是对线程的"描述"。TCB包含了管理一个线程所需的全部信息:
cpp
// 简化的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就是线程的"身份证"和"档案",完整描述了线程的所有属性和状态。
2. "再组织" - 管理所有TCB
pthread库通过以下数据结构组织所有线程的TCB,实现快速访问与调度:
-
TCB索引表
- 全局数组
struct pthread *__thread_list[MAX_THREADS]
- 通过用户级线程ID(
pthread_t
)作为下标直接定位TCB(#ref1) - 示例:
TCB = __thread_list[(unsigned long)pthread_self % MAX_THREADS]
- 全局数组
-
LWP ↔ TCB 映射表
- 哈希表
hash_map<pid_t LWP, struct pthread* TCB>
- 用途:内核通过LWP查询TCB(如处理信号时需修改TCB信号掩码)(#ref2)
- 哈希表
-
线程状态队列
队列类型 数据结构 用途 就绪队列 红黑树(按优先级) 用户级调度(配合LWP内核调度) 等待队列 链表 阻塞在条件变量/互斥锁的线程 分离线程回收队列 链表 自动回收已终止的分离线程
3. 与内核的协作
虽然pthreads库在用户空间管理线程,但它需要内核的支持来实现真正的并发执行:
-
线程创建 :当调用
pthread_create()
时:-
库函数分配TCB结构体和线程栈
-
初始化TCB中的各种字段
-
将新TCB添加到全局线程列表中
-
调用
clone()
系统调用,请求内核创建真正的执行上下文
-
-
线程调度:虽然pthreads库管理线程状态,但实际的调度决策由内核做出。库需要与内核协作处理线程的阻塞、唤醒等状态转换。
-
同步原语:互斥锁、条件变量等同步机制虽然在用户空间实现了一部分优化(如futex),但在需要时仍然会通过系统调用进入内核。
线程退出和清理
当线程结束时,pthreads库需要:
-
保存线程返回值到TCB中
-
如果线程是joinable的,将其标记为已终止但资源尚未回收
-
如果是detached的,立即回收TCB和栈空间
-
从全局线程列表中移除该TCB
总结:分层抽象的艺术
pthread库的线程管理是用户态与内核态协作的典范:
- 描述层 :
- 通过TCB结构体封装线程全生命周期状态
pthread_t
作为TCB指针提供进程内唯一标识
- 组织层 :
- 全局索引表实现 O(1) 复杂度访问
- 队列结构管理不同状态线程
- 内核桥接 :
- 将POSIX API转化为
clone
/futex
等系统调用 - 维护LWP↔TCB映射保证内核操作可定位用户态资源
- 将POSIX API转化为
3. 线程终止
三种线程终止方法详解
1. 从线程函数 return
这是最自然、最推荐的线程终止方式。
工作原理:
-
当线程执行到其启动函数的
return
语句时,线程会正常结束 -
返回值可以通过
pthread_join
获取
注意事项:
-
主线程中从
main
函数 return 会终止整个进程 -
返回的指针必须指向全局数据或堆上分配的内存,不能指向线程栈上的局部变量
2. 调用 pthread_exit 终止自己
这种方式允许线程在任何地方主动终止自己,而不必返回到函数开头。
函数原型:
cpp
void pthread_exit(void *value_ptr);
使用场景:
-
在线程执行的任何地方需要立即退出
-
当线程需要返回一个值,但无法通过函数返回实现时
重要注意事项:
-
内存管理 :
value_ptr
不能指向线程栈上的局部变量,因为线程退出后栈会被销毁 -
主线程使用 :在主线程中调用
pthread_exit
会终止主线程,但其他线程会继续运行,直到所有线程都结束 -
清理处理程序 :调用
pthread_exit
会执行线程的清理处理程序(通过pthread_cleanup_push
注册的)
3. 调用 pthread_cancel 取消另一个线程
这种方式允许一个线程请求终止同一进程中的另一个线程。
函数原型:
cpp
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
注册清理函数
-
-
取消的协作性:
-
线程取消是一种协作机制,目标线程必须配合才能被取消
-
如果线程禁用取消或从不到达取消点,它将无法被取消
-
-
线程分离:
-
如果线程被设置为分离状态(detached),则不需要其他线程调用
pthread_join
来回收资源 -
分离线程终止后,系统会自动回收其资源
-
4. 线程等待
为什么需要线程等待?
-
资源泄漏防止:
-
线程退出后,其栈空间和线程控制块(TCB)等资源不会自动释放
-
这些资源会一直占用进程的地址空间,导致"僵尸线程"问题
-
类似于进程中的僵尸进程,如果不处理,会逐渐耗尽系统资源
-
-
地址空间复用:
-
新创建的线程不会复用已退出线程的地址空间
-
每次创建新线程都会分配新的栈空间和控制结构
-
如果不回收旧线程的资源,进程的内存占用会不断增长
-
-
同步需求:
-
主线程可能需要等待工作线程完成特定任务后才能继续执行
-
线程间需要协调执行顺序,确保数据一致性
-
-
结果获取:
-
工作线程可能需要将执行结果返回给主线程或其他线程
-
pthread_join
是获取线程返回值的标准机制
-
pthread_join 函数深度解析
函数原型
cpp
int pthread_join(pthread_t thread, void **value_ptr);
- thread :目标线程的
pthread_t
标识符(由pthread_create
返回) - value_ptr :二级指针,用于接收线程退出状态
- 若传递
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
返回的值必须位于全局内存或堆中(禁止指向栈变量)
综合示例:
cpp
#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);
}
运行结果:


重要注意事项和最佳实践
-
线程状态要求:
-
只能对非分离(joinable)状态的线程调用
pthread_join
-
如果线程处于分离(detached)状态,调用
pthread_join
会失败并返回 EINVAL
-
-
一对一关系:
-
每个线程只能被一个线程 join 一次
-
多次 join 同一个线程会导致未定义行为
-
-
内存管理责任:
-
通过
pthread_join
获取的返回值内存必须由调用者负责释放 -
线程不应该返回指向其栈上数据的指针
-
-
错误处理:
-
总是检查
pthread_join
的返回值 -
常见的错误码:
-
ESRCH
:没有找到与给定线程ID对应的线程 -
EINVAL
:线程不是可连接状态,或者另一个线程已经在等待此线程 -
EDEADLK
:死锁情况,例如线程尝试join自己
-
-
-
超时处理:
-
pthread_join
没有超时机制,会无限期等待 -
如果需要超时功能,可以考虑使用条件变量或其他同步机制
-
5. 线程分离
默认情况:可连接线程(Joinable Thread)
-
新创建的线程默认是可连接的(joinable)
-
这类线程终止后,必须由其他线程调用
pthread_join
来回收资源 -
如果不进行 join 操作,线程资源会泄漏,形成"僵尸线程"
分离线程(Detached Thread)
-
分离线程在终止时会自动释放所有资源
-
不需要也不能被其他线程 join
-
适用于不需要获取线程返回值的场景
pthread_detach 函数详解
函数原型
cpp
int pthread_detach(pthread_t thread);
参数说明
thread
:要分离的线程ID
返回值
-
成功返回 0
-
失败返回错误码(如 ESRCH 表示线程不存在,EINVAL 表示线程已经是分离状态)
使用方式
1. 创建时分离(推荐)
cpp
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. 其他线程分离目标线程
cpp
pthread_create(&tid, NULL, worker, NULL);
pthread_detach(tid); // 主线程主动分离子线程
- 适用场景:主线程不关心子线程结果,但需控制分离时机
3. 线程自我分离
cpp
void* worker(void* arg) {
pthread_detach(pthread_self()); // 线程内自行分离
// ... 业务逻辑
return NULL;
}
- 优势:避免主线程忘记分离,适合动态线程池
示例:
cpp
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 是互斥的
-
一个线程不能同时是可连接和分离的
-
如果尝试 join 一个已分离的线程,会返回 EINVAL 错误
-
如果尝试分离一个已分离的线程,也会返回 EINVAL 错误
2. 分离时机
-
可以在线程创建后的任何时间点分离线程
-
但最好在知道不需要线程返回值时立即分离
3. 资源回收
-
分离线程终止时,系统会自动回收其栈空间和线程控制块
-
但线程分配的其他资源(如打开的文件、动态分配的内存等)仍需程序员负责清理
4. 错误处理
总是检查 pthread_detach
的返回值:
cpp
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
-
提高程序的可维护性
-
-
注意事项:
-
分离后不能再 join
-
仍需负责清理线程分配的非线程特有资源
-
总是检查分离操作的返回值
-
6. 线程封装
代码如下:
Thread.hpp:
cpp
#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;
};
}
cpp
#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系统,常用于多线程程序的调试与管理。