一、POSIX线程库
POSIX 线程(通常称为 pthreads)是 IEEE 制定的操作系统线程 API 标准。Linux 系统通过 glibc 库实现了这个标准,提供了创建和管理线程的一系列函数。
核心特性:
- 命名约定:绝大多数函数都以 pthread_ 开头,这使得代码中线程相关的操作非常清晰易辨。
- 头文件:使用 #include <pthread.h> 来包含所有数据类型和函数的声明。
- 链接库:编译时必须添加 -lpthread 或 -pthread 链接选项来链接线程库。
-pthread 通常是更推荐的选择,因为它除了链接库之外,还可能定义必要的预处理宏,确保代码的可移植性。



二、线程创建
1、pthread_creat
- 函数原型:int pthread_create(pthread_t thread,const pthread_attr_t attr,void(start_routine)(void),voidarg);
- 参数:
- thread:输出型参数,返回用户层线程 ID。
- attr:设置线程的属性,为 nullptr 表示使用默认属性。
- start_routine:是个函数地址,线程启动后要执行的函数。
- arg:传给启动线程的参数。
- 返回值:创建成功返回0,创建失败返回对应的错误码。
比如下面这段代码,我们让其创建出一个线程,并观察其 PID:
            
            
              cpp
              
              
            
          
          #include<iostream>
#include<string>
#include<unistd.h>
#include<pthread.h>
void *threadrun(void *args)
{
    std::string name = (const char*)args;
    while(true)
    {
        std::cout << "我是新线程,name:" << name << " pid: " << getpid() << std::endl;
        sleep(1);
    }
    return nullptr;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadrun, (void*)"thread-1");
    while(true)
    {
        std::cout << "我是主线程..." << std::endl;
        sleep(1);
    }
    return 0;
}
我们观察到主线程与新创建的线程的 PID 相同,这样证明它们是同属于一个进程的。

注意:创建新线程后,两个线程同时向显示屏输出,会造成数据竞争,导致打印的输出信息混在了一起。
并且我们也可以通过指令 ps -aL 查看当前操作系统中的所有轻量级线程, -L 选项:打印线程信息。

其中 LWP (light weight proces)就是指一个轻量级进程的 ID ,如果一个线程的 PID == LWP ,我们就称该线程为主线程。

给线程传递的参数和返回值可以是任意类型(包括对象)。
            
            
              cpp
              
              
            
          
          #include<iostream>
#include<pthread.h>
#include<string>
#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 *routine(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, routine, 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、pthread_self
- 函数原型:pthread_t pthread_self(void);
- 作用:获取当前线程 ID
- 返回值:pthread_t 类型,表示当前线程的唯一标识符
- 错误码:永不失败(总是成功)

示例:
            
            
              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;
}
3、LWP 与 PID
在 Linux 系统中,实际上存在两种不同意义上的"线程ID",它们处于不同的抽象层次,有不同的用途。
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
()获取。
LWP (Light Weight Process ID) / TID (Thread ID) ------ 内核态/系统级别 ID
来源 :由 Linux 内核分配和管理。
本质 :这是一个 pid_t 类型的整数,与进程 PID 属于同一种类型。内核为每一个调度实体(无论是进程还是线程)都分配一个唯一的 ID 。
作用域 :系统全局有效。在整个操作系统范围内唯一标识一个调度任务。
用途 :用于系统级 的监控、调度和调试。 top,ps,perf 等工具看到和使用的就是这个 ID 。
特点:
在Linux 中,可以通过系统调用 gettid
()来获取。
主线程的 LWP 等于进程的 PID 。
其他线程的 LWP 是内核分配的新 ID 。


上面我们就提到每一个线程都有自己独立的栈结构,其中主线程采用的栈是进程地址空间中的原生栈,而其余线程采用的栈就是在共享区中开辟的。除此之外,每个线程都有自己的 struct pthread 结构,当中包含了对应线程的各种属性;每个线程还有自己的线程局部存储,当中包含了对应线程被切换时的上下文数据。
每一个新线程在共享区都存在一块对其进行描述的区域,所以要找到一个用户级线程,只需找到该线程内存块的起始地址,这样就能获取到该线程的各种信息。所以用户层线程 ID 本质就是一个指向线程起始位置的虚拟地址。

每个线程在创建后都需拥有独立的栈结构。原因在于每个线程都具备自身的调用链,而执行流的本质正是调用链。此栈空间能够保存一个执行流在运行期间所产生的临时变量,并且在函数调用时进行入栈操作。而 LWP 则只是操作系统在内核唯一标识轻量级进程的编号。

三、线程终止
如果需要只终止某个线程而不终止整个进程,可以有三种方法:
- 从线程函数 return 。这种⽅法对主线程不适⽤,从 main 函数 return 相当于调⽤ exit。
- 线程可以调⽤ pthread_exit 终⽌⾃⼰。
- ⼀个线程可以调⽤ pthread_cancel 终⽌同⼀进程中的另⼀个线程。
1、从线程函数 return

2、pthread_exit 终止自身线程
线程不能用 exit 终止,因为 exit 是终止进程的,再线程内调用会直接终止整个进程。
pthread_exit 这种方式允许线程在任何地方主动终止自己,而不必返回到函数开头。
void pthread_exit(void *value_ptr);

3、 pthread_cancel 取消其他线程
这种方式允许一个线程请求终止同一进程中的一个已经存在的线程,其用法如下:
- 函数原型:int pthread_cancel(pthread_t thread);
- 参数:thread:要取消的线程 ID。
- 返回值:取消成功返回 0;取消失败,返回对应的错误码。
比如下面我们创建一个线程,让其通过 pthread_cancel取消,再让线程等待获取其返回值。
            
            
              cpp
              
              
            
          
          #include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;
int g_val = 100;
void *threadRoutine(void *args)
{
    const char *name = (const char*)args;
    int cnt = 5;
    while (true)
    {
        printf("%s, pid: %d, g_val: %d, &g_val: %p\n", name, getpid(), g_val, &g_val);
        sleep(1);
        cnt--;
        if(cnt == 0) break;
    }
    pthread_exit((void *)200);
}
int main()
{
    pthread_t pid;
    pthread_create(&pid, nullptr, threadRoutine, (void *)"Thread 1");
    sleep(1);
    pthread_cancel(pid);
    void *ret;
    pthread_join(pid, &ret);
    cout << "main thread quit..., Thread 1 return val: " << (long long int)ret << endl;
    return 0;
}
如果一个线程被取消,它会返回一个名为 PTHREAD_CANCELED 的宏,其值为 -1。
其实我们也能够通过新线程来取消我们的主线程,主线程会停止运行,但其他线程并不会收到任何影响。但这种做法并不符合我们的一般逻辑,所以并不推荐。
四、线程等待
1、为什么需要线程等待?

2、pthread_join
其实一个线程被创建出来也是需要被等待的,如果不等待,也会发生类似进程的"僵尸"问题,即内存泄漏。而线程等待我们需要使用的接口是 pthread_join。
- 函数原型:int pthread_join(pthread_t thread, void **retval);
- 参数:
- thread:要等待的线程 ID。
- retval:输出型参数,获取线程函数的返回值,如果不关心可传 nullptr。
- 返回值:等待成功返回 0;等待失败,返回对应的错误码。
比如下面我们创建一个线程,让其退出后返回一个值,让线程等待获取这个值。
            
            
              cpp
              
              
            
          
          #include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;
int g_val = 100;
void *threadRoutine(void *args)
{
    const char *name = (const char *)args;
    int cnt = 5;
    while (true)
    {
        printf("%s, pid: %d, g_val: %d, &g_val: %p\n", name, getpid(), g_val, &g_val);
        sleep(1);
        cnt--;
        if (cnt == 0)
            break;
    }
    return (void *)100;
}
int main()
{
    pthread_t pid;
    pthread_create(&pid, nullptr, threadRoutine, (void *)"Thread 1");
    void *ret;
    pthread_join(pid, &ret);
    cout << "main thread quit..., Thread 1 return val: " << (long long int)ret << endl;
    return 0;
}
其中需要注意的是:pthread_exit 或者 return 返回的指针所指向的内存单元必须是全局的或者是动态分配的,不能在线程函数的栈上分配,因为当其他线程得到这个返回指针时,线程函数已经退出了。

五、线程分离
1、为什么需要线程分离?
- 默认情况下,新创建的线程是 joinable 的,线程退出后,需要对其进行 pthread_join 操作,否则无法释放资源,从而造成系统泄漏。
- 如果不关心线程的返回值,join 是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
# 分离线程(Detached Thread)
- 
分离线程在终止时会自动释放所有资源 
- 
不需要也不能被其他线程 join 
- 
适用于不需要获取线程返回值的场景 
2、pthread_detach
- 函数原型:int pthread_detach(pthread_t thread);
- 参数:thread:要分离的线程 ID。
- 返回值:分离成功返回 0;分离失败,返回对应的错误码。
比如下面我们创建五个新线程后让这五个新线程分离,此后主线程就不需要在对这五个新线程进行回收了。同时因为主线程并不需要等待其他线程,也能继续执行后续代码。
            
            
              cpp
              
              
            
          
          #include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
#include <cstdio>
#include <cstring>
void *routine(void *args)
{
    // 新线程自己分离
    pthread_detach(pthread_self());
    std::cout << "新线程被分离" << std::endl;
    int cnt = 5;
    while (cnt--)
    {
        std::cout << "main线程名字:" << std::endl;
        sleep(1);
    }
    return nullptr;
}
// 如果主线程不想再关心新线程,而是当新线程结束的时候让他自己释放,要怎么做?
// 可以设置新线程为分离状态
// 技术层面:joinable 线程默认等待
//          !joinable/detach 新线程结束后自己退出
// 理解层面:线程分离可以是主线程分离新线程,也可以是新线程把自己分离出齐
//          但是在进程地址空间中,被分离的线程依旧可以访问进程的所有资源,主线程不再等待新线程
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, routine, (void*)"thread-1");
    // 主线程分离新线程
    // pthread_detach(tid);
    // std::cout << "新线程被分离" << std::endl;
    int cnt = 5;
    while (cnt--)
    {
        std::cout << "main线程名字:" << std::endl;
        sleep(1);
    }
    int n = pthread_join(tid, nullptr);
    if(n != 0)
    {
        std::cout << "pthread_join error: " << strerror(n) << std::endl;
    }
    else
    {
        std::cout << "pthread_join success: " << strerror(n) << std::endl;
    }
    return 0;
}
六、一个简单的多线程
            
            
              cpp
              
              
            
          
          #include<iostream>
#include<string>
#include<vector>
#include<unistd.h>
#include<cstdio>
#include<cstring>
#include<pthread.h>
// 创建多线程
const int num = 10;
void *routine(void *args)
{
    std::string name = static_cast<const char *>(args);
    delete (char *)args;
    int cnt = 5;
    while(cnt--)
    {
        std::cout << "new线程名字:" << name << std::endl;
        sleep(1);
    }
    return nullptr;
}
int main()
{
    std::vector<pthread_t> tids;
    for (int i = 0; i < num; i++)
    {
        pthread_t tid;
        // char id[64];
        // snprintf(id, sizeof(id), "thread-%d", i);
        char *id = new char[64];
        snprintf(id, 64, "thread-%d", i);
        int n = pthread_create(&tid, nullptr, routine, id);
        if(n == 0)
            tids.push_back(tid);
        else
            continue;
        // sleep(1);
    }
    for (int i = 0; i < num; i++)
    {
        // 一个一个等待
        int n = pthread_join(tids[i], nullptr);
        if(n == 0)
        {
            std::cout << "等待新线程成功" << std::endl;
        }
    }
    return 0;
}这段代码修改之前存在一个典型的多线程数据竞争与未定义行为 的 bug,具体问题出在线程参数的传递方式 上,导致线程参数指向的临时内存被重复覆盖。

解决方案:确保每个线程获取到独立且不会被修改的参数。

七、线程ID及进程地址空间布局
1、用户 ID 与 LWP
我们知道 Linux 没有真正的线程,只有轻量级进程。为了解决和用户的鸿沟,所以创建了 pthread 库封装轻量级进程,形成原生线程库。
那刚才写的代码编译是不是可执行程序?是,所以他就是 ELF 格式,我们的 pthread 库是动态库,所以也是 ELF 格式。
要想搞清楚用户级原生线程库的线程 ID 与内核 LWP 的区别,我们首先得明白所使用的原生线程库本质其实就是一个动态库,在可执行程序加载时,会形成进程,进行动态链接和动态地址重定向,因此动态库会被加载到内存共享区中,并映射到当前进程的地址空间中。


所以 pthread 库会加载到物理内存,映射到地址空间里面,而 pthread_create 和 pthread_join 是起始地址 + 偏移量的方式访问到 pthread 库。
# 结论:进程的代码区可以访问到 pthread 库内部的函数或数据!
而线程其实是在库中维护的,因为 Linux 只有轻量级进程,所以库中肯定有很多的线程,有些线程等待被 join ,有些线程被分离,有些线程刚创建。所以我们的库内部也需要把线程先描述在组织!


mmap 区域的动态库的内容如图所示。我们每次创建一个线程就会创建描述 tcb 的结构,以及包括线程局部存储和线程的独立栈结构。再创建一个线程也就继续创建三个这样的结构,但是两个线程的地址不一定连续。
之前程序打印出来的 pthread_create 的输出性参数 pthread_t 是一个很大的数字,这个数字不是 LWP ,而是我们线程在库中管理块的起始地址。
在 struct pthread 线程控制块里面有一个属性 void* ret ,当我们控制块对应的线程执行完代码后,返回值就会写入 ret 里面,这个线程运行结束了,但是他对应的控制块并没有释放,所以线程需要 pthread_join ,所以我们 pthread_join 函数需要传入线程 tid ,即线程控制块的起始地址。此时 join 再把 ret 返回,就拿到线程的退出信息,再把线程的管理块全部释放掉,就得到返回结果并且解决内存泄露问题!
所以我们在自己的代码区里面调用 pthread_create 就会在动态库内存创建线程管理块。开头是线程 tcb 控制块,包含线程属性以及 void* ret ,当线程结束时,会把返回值拷贝到线程控制块 ret 里面,因为新、主线程共享地址空间,所以主线程只要拿着 tid 起始地址就可以找到退出线程的线程控制块,所以线程结束后内存泄漏的原因是控制块没有释放。所以 join 时把 ret 拷贝到输出型参数里,因此就需要 void** 。
为什么我们 ps -aL 查不到线程的信息。因为内核的 LWP 自动释放,但是库里面的东西没有释放。
每一个线程都有自己独立的栈结构 ,主线程用地址空间中的栈,每个线程用自己 pthread 库内部的控制块的栈。

pthread_create 会创建在库中创建一个线程控制块,把他的起始地址写入线程 ID 里面,然后还要调用系统调用 clone 在内核中创建 LWP 轻量级进程,还需要把代码区中 routine 的入口函数以及创建的线程栈地址传入给系统调用,此时 CPU 调度这个线程是就会执行用户定义的代码,形成的临时数据会入栈保存在线程栈中,每个线程都是这样做的。
那用户线程是如何和 LWP 联动起来的呢?
用户线程只需要在库里创建好描述线程的相关属性即可,然后给底层指明要执行什么方法 (带饭),临时数据保存在栈里面,此时你用户空间的属性就不用变了,在用户的视角看来你就是线程,但是真正执行其实是内核轻量级进程。最后当线程执行完毕后,把返回结果写入 pthread 的 ret 里面。这个过程就是用户线程和 LWP 联动的过程!

所以 Linux 线程叫做用户级线程,因为 Linux 的线程实现是在库里的,而库被映射到用户空间,线程相关信息在这里维护。而轻量级进程调度,优先级等信息用户线程不保存,由底层负责调度!
Linux 用户级线程 : 内核 LWP = 1 : 1,而有一些 OS 用户级线程 : 内核 LWP = 1 : n。

2、内核源码

3、mmap文件映射
- 允许用户空间程序将文件或设备的内容直接映射到进程的虚拟地址空间中。通过 mmap,程序可以高效地访问文件数据,而无需通过传统的 read 或 write 系统调用进行数据的复制。
- mmap还可以用于实现共享内存,允许不同进程间共享数据。

4、线程栈
虽然 Linux 将线程和进程不加区分的统⼀到了 task_struct ,但是对待其地址空间的 stack 还是
有些区别的。
- 对于 Linux 进程或者说主线程,简单理解就是 main 函数的栈空间,在 fork 的时候,实际上就是复制了父亲的 stack 空间地址,然后写时拷贝 (cow) 以及动态增长。如果扩充超出该上限则栈溢出会报段错误(发送段错误信号给该进程)。进程栈是唯一可以访问未映射页而不一定会发生段错误 ------ 超出扩充上限才报。
- 然而对于主线程生成的子线程而言,其线程栈 stack 将不再是向下生长的,而是事先固定下来的 。线程栈一般是调用 glibc/uclibc 等的 pthread 库接口 pthread_create 创建的线程,在文件映射区(或称之为共享区)。其中使用 mmap 系统调用,这个可以从 glibc 的 nptl/allocatestack.c 中的 allocate_stack 函数中看到:
            
            
              cpp
              
              
            
          
          mem = mmap (NULL, size, prot, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);此调用中的 size 参数的获取很是复杂,你可以手工传入 stack 的大小,也可以使用默认的,一般而言就是默认的 8M 。这些都不重要,重要的是,这种 stack 不能动态增长,一旦用尽就没了,这是和生成进程的 fork 不同的地方 。在 glibc 中通过 mmap 得到了 stack 之后,底层将调用 sys_clone 系统调用:
            
            
              cpp
              
              
            
          
          int sys_clone(struct pt_regs *regs)
{
    unsigned long clone_flags;
    unsigned long newsp;
    int __user *parent_tidptr, *child_tidptr;
    clone_flags = regs->bx;
    //获取了mmap得到的线程的stack指针
    newsp = regs->cx;
    parent_tidptr = (int __user *)regs->dx;
    child_tidptr = (int __user *)regs->di;
    if (!newsp)
        newsp = regs->sp;
    return do_fork(clone_flags, newsp, regs, 0, parent_tidptr, child_tidptr);
}因此,对于子线程的 stack ,它其实是在进程的地址空间中 mmap 出来的一块内存区域,原则上是线程私有的,但是同一个进程的所有线程生成的时候,是会浅拷贝生成者的 task_struct 的很多字段,如果愿意,其它线程也还是可以访问到的,一定要注意。

5、线程的局部存储
我们知道普通的全局变量是被所有线程所共享的,如果想让该全局变量被每个线程各自私有一份,可以在定义全局变量的前面加上 __thread ,这并不是语言给我们提供的,而是编译器给我们提供。并且 __thread 只能用来修饰内置类型,不能用来修饰自定义类型。
比如我们创建五个线程,并用 __thread 定义一个全局变量 val,在各个新线程中打印其值域地址。
            
            
              cpp
              
              
            
          
          #include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
using namespace std;
__thread int val = 100;
void *Routine(void *arg)
{
    pthread_detach(pthread_self());
    char *msg = (char *)arg;
    printf("I am %s...val:%d,&val:%p\n", msg, val, &val);
    sleep(1);
    while(true);
}
int main()
{
    pthread_t tid[5];
    for (int i = 0; i < 5; i++)
    {
        char buffer[64] = {'\0'};
        snprintf(buffer, sizeof(buffer), "thread %d", i);
        pthread_create(&tid[i], nullptr, Routine, buffer);
        sleep(1);
    }
    sleep(3);
    return 0;
}
线程局部存储 TLS 的核心作用是让变量在 "每个线程内独立",同时在 "线程内全局共享",解决了 "线程间变量隔离" 与 "线程内变量复用" 的矛盾。
八、线程封装
pthread 库是 C 语言实现的,使用时需要手动管理大量细节,直接调用容易出错;封装后能把这些细节 "隐藏" 起来,用户无需关注底层逻辑。
线程封装的核心目的是降低多线程编程的复杂度、提升代码安全性和可复用性。
            
            
              cpp
              
              
            
          
          // Thread.hpp
#ifndef THREAD__H_
#define THREAD__H_
#include <iostream>
#include <string>
#include <pthread.h>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <functional>
namespace ThreadMoudle
{
    // 原子计数器:用于生成唯一的线程名称(保证多线程环境下名称不重复)
    static uint32_t number = 1;
    class Thread
    {
        // 线程要执⾏的外部⽅法,我们不考虑传参,后续有std::bind来进⾏类间耦合
        using func_t = std::function<void()>; // 一个 "函数容器",可以存储、传递各种可调用对象
                                              // (只要函数、lambda 表达式、函数对象、类成员函数等)
                                              // 只要它们的签名符合 void()(无参、无返回值)。
    private:
        // 设置线程为分离状态(辅助函数,仅内部调用)
        void EnableDetach()
        {
            std::cout << "线程被分离了" << std::endl;
            _isdetach = true;
        }
        // 标记线程为运行中(辅助函数,仅内部调用)
        void EnableRunning()
        {
            _isrunning = true;
        }
        // 线程入口函数(必须为静态成员,因为pthread_create要求函数为void*(*)(void*)格式,无this指针)
        static void *Routine(void *args) // 不加static就是非静态成员函数,属于类的对象,默认包含this指针
                                         // 设为static就是静态成员函数,属于类本身,而不属于变量了,就没有this指针了
        {
            Thread *self = static_cast<Thread *>(args); // 将void*转换为Thread对象指针
            self->EnableRunning();                      // 标记线程为运行中
            // 在 Start() 中,当 _isdetach 为 true 时调用 Detach(),是为了将 "用户预期的分离状态" 落实到
            // 实际的线程状态中;而 _isdetach 为 false 时不调用,是为了保持线程默认的可连接状态,确保后续
            // Join() 等操作有效。
            if (self->_isdetach) // 若已设置为分离,调用Detach完成分离逻辑
                self->Detach();
            self->_func(); // 回调处理,执行用户传入的函数对象
            return nullptr; // 线程返回值(可通过pthread_join获取)
        }
    public:
        // 构造函数:接收线程要执行的函数对象,初始化线程属性并生成唯一名称
        Thread(func_t func)
            : _tid(0) // 线程ID初始化为0
            ,_isdetach(false) // 初始为非分离状态
            ,_isrunning(false) // 初始为未运行状态
            ,_res(nullptr) // 线程返回值初始为nullptr
            ,_func(func) // 绑定要执行的函数
        {
            _name = "thread-" + std::to_string(number++); // 生成唯一线程名(如thread-1, thread-2...)
        }
        // 设置线程为分离状态:分离后线程资源由系统自动回收,无需join
        void Detach()
        {
            if (_isdetach) // 若已为分离状态,直接返回
                return;
            if (_isrunning) // 若线程已运行,调用pthread_detach标记为分离
                pthread_detach(_tid);
            EnableDetach(); // 标记内部状态为分离
        }
        // 启动线程:创建 pthread 并标记为运行中
        bool Start()
        {
            if (_isrunning) // 若线程已在运行,直接返回失败
                return false;
            // 调用pthread_create创建线程,传入Routine作为入口,this作为参数传递给Routine
            // 这样在Routine内部就可以通过static_cast<Thread *>(args)还原出Thread对象指针(self)
            // 从而访问类的成员(_func) 
            int n = pthread_create(&_tid, nullptr, Routine, this);
            if (n != 0)
            {
                std::cerr << "create thread error: " << strerror(n) << std::endl;
                return false;
            }
            else
            {
                std::cout << _name << " create success" << std::endl;
                return true; // 启动成功
            }
        }
        // 终止线程:通过pthread_cancel请求线程终止
        bool Stop()
        {
            if (_isrunning)
            {
                // 发送取消请求给线程
                int n = pthread_cancel(_tid);
                if (n != 0)
                {
                    std::cerr << "cancel thread error: " << strerror(n) << std::endl;
                    return false;
                }
                else
                {
                    _isrunning = false; // 标记为非运行状态
                    std::cout << _name << " stop" << std::endl;
                    return true;
                }
            }
            return false;
        }
        // 等待线程结束:通过pthread_join回收线程资源并获取返回值
        void Join()
        {
            if (_isdetach) // 分离状态的线程无需join,直接返回
            {
                std::cout << "你的线程已经分离了,不能进行join" << std::endl;
                return;
            }
            // 等待线程结束,将返回值存入_res
            int n = pthread_join(_tid, &_res);
            if (n != 0)
            {
                std::cerr << "join thread error: " << strerror(n) << std::endl;
            }
            else
            {
                std::cerr << "join success: " << std::endl;
            }
        }
        ~Thread()
        {}
    private:
        pthread_t _tid;    // 线程ID(pthread库的线程标识符)
        std::string _name; // 线程名称(用于标识和调试)
        bool _isdetach;    // 是否为分离状态
        bool _isrunning;   // 是否正在运行
        void *_res;        // 线程返回值(通过pthread_join获取)
        func_t _func;      // 线程要执行的函数对象
    };
}
#endif
            
            
              cpp
              
              
            
          
          // main.cc
#include "Thread.hpp"
#include<unistd.h>
using namespace ThreadMoudle;
int main()
{
    // Thread t(...):创建Thread类的实例t,调用Thread的构造函数
    // 参数是一个lambda 表达式([](){ ... }),这个表达式定义了子线程启动后要执行的具体逻辑
    // lambda 表达式是一个匿名函数,这里的[](){ ... }等价于一个 "无参数、无返回值的函数"
    Thread t([](){
        while(true)
        {
            std::cout << "我是一个新线程" << std::endl;
            sleep(1);
        }
    });
    // t.Detach();
    t.Start();
    // t.Detach();
    sleep(5);
    t.Stop();
    sleep(5);
    t.Join();
    return 0;
}

pthread_setname_np 和 pthread_getname_np 是 POSIX 线程库中用于设置和获取线程名称 的扩展函数(np 表示 "non-portable",即非标准但被广泛支持的扩展)。它们的主要作用是为线程指定一个可读性名称,方便调试和线程状态监控。
1、pthread_setname_np 和 pthread_getname_np



            
            
              cpp
              
              
            
          
          #include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
// 线程入口函数
void *thread_func(void *arg) {
    // 获取当前线程ID
    pthread_t tid = pthread_self();
    
    // 设置线程名称
    if (pthread_setname_np(tid, "worker-thread") != 0) {
        perror("pthread_setname_np failed");
    }
    
    // 获取并打印线程名称
    char name[16];
    if (pthread_getname_np(tid, name, sizeof(name)) == 0) {
        printf("线程名称:%s,正在运行...\n", name);
    }
    
    sleep(5); // 模拟线程工作
    return NULL;
}
int main() {
    pthread_t tid;
    // 创建线程
    if (pthread_create(&tid, NULL, thread_func, NULL) != 0) {
        perror("pthread_create failed");
        return 1;
    }
    
    // 主线程等待子线程结束
    pthread_join(tid, NULL);
    return 0;
}