> 技术文档 > 【Linux系统】线程控制

【Linux系统】线程控制


1. POSIX线程库 (pthreads)

POSIX线程(通常称为pthreads)是IEEE制定的操作系统线程API标准。Linux系统通过glibc库实现了这个标准,提供了创建和管理线程的一系列函数

核心特性

  • 命名约定:绝大多数函数都以 pthread_ 开头,这使得代码中线程相关的操作非常清晰易辨。

  • 头文件:使用 #include  来包含所有数据类型和函数的声明。

  • 链接库:编译时必须添加 -lpthread 或 -pthread 链接选项来链接线程库。

    • -pthread 通常是更推荐的选择,因为它除了链接库之外,还可能定义必要的预处理宏,确保代码的可移植性。

为什么需要 -lpthread 选项?

这是一个非常重要的实践点。C语言的标准库(libc)默认不包含pthread函数的具体实现。这些实现存在于一个独立的共享库文件中,通常是 libpthread.so

  • -l 选项告诉链接器(ld)要去链接一个库。

  • pthread 是 libpthread.so 的简写(链接器会自动添加 lib 前缀和 .so 后缀)。

因此,-lpthread 的本质是:“链接器,请将我们的程序与名为 libpthread.so 的共享库链接起来,以便解析所有以 pthread_ 开头的函数。”


2. 线程创建

函数原型与核心机制

#include 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 函数的开始处执行,直到:

    1. 函数返回(线程隐式终止)

    2. 调用 pthread_exit()(线程显式终止)

    3. 被其他线程取消(pthread_cancel()

  • 返回值:线程函数的返回值可以通过 pthread_join() 获取。

4. void *arg - 线程参数

  • 用途:传递给线程函数的参数。

  • 灵活性:由于是 void* 类型,可以传递任何数据类型的地址。

  • 注意事项

    • 确保参数在线程使用期间保持有效

    • 如果传递栈上变量的地址,要确保原函数不会在线程使用前返回

    • 通常使用动态分配的内存或全局变量传递数据

返回值与错误处理

  • 成功:返回 0

  • 失败:返回错误码(非零值),不设置 errno

  • 常见错误码

    • EAGAIN:系统资源不足,无法创建线程,或已超过线程数量限制

    • EINVALattr 参数无效

    • EPERM:没有权限设置指定的调度策略或参数

示例:

#include #include #include #include #include void *routine(void *arg){ std::string name = static_cast(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$ ./testmain主线程, pid: 221797我是一个新线程: thread-1, pid: 221797main主线程, pid: 我是一个新线程: thread-1221797, pid: 221797main主线程, pid: 221797我是一个新线程: thread-1, pid: 221797main主线程, pid: 221797我是一个新线程: thread-1, pid: 221797main主线程, pid: 221797我是一个新线程: thread-1, pid: 221797main主线程, pid: 221797main主线程, pid: 221797main主线程, pid: 221797main主线程, pid: 221797main主线程, pid: 221797main主线程, 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 CMD

PID (进程ID): 两个线程都有相同的PID (221797)。这证明了它们属于同一个进程。

LWP (轻量级进程ID): 每个线程有不同的LWP (221797 和 221798)

  • LWP是线程在内核中的唯一标识符。

  • 主线程的LWP通常等于PID。

  • 其他线程有自己唯一的LWP。

可以直观感受到,线程本质上就是共享相同地址空间和其他资源的\"轻量级进程\"

那tid是啥呢?我们也可以将tid打印出来看一下,通过 pthread 库中函数 pthread_self 的返回值得到

pthread_self - 获取当前线程ID

函数原型

#include pthread_t pthread_self(void);
  • 参数:无
  • 返回值pthread_t 类型,表示当前线程的唯一标识符
  • 错误码:永不失败(总是成功)

线程 ID (pthread_t) 的本质

  • 数据类型
    • 通常为 unsigned long(Linux 实现)
    • 具体类型由操作系统实现定义,可能是整型或结构体 
  • 生命周期
    • 正在运行的线程 ID 唯一
    • 终止后 ID 可被新线程复用(非永久唯一)
  • 作用域:仅在同一进程内有效,跨进程无意义

示例:

#include #include #include #include #include void showid(){ printf(\"tid: %lu\\n\", pthread_self());}void *routine(void *arg){ std::string name = static_cast(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_joinpthread_cancel等)。内核完全不知道这个ID的存在。

  • 用途:用于同一进程内的线程间操作和同步。

  • 特点

    • 可移植性差。不同操作系统或不同libc实现可能用不同的方式表示pthread_t(结构体、整数等)。

    • 使用pthread_equal()来比较,不要直接用==(为了可移植性)。

    • 使用pthread_self()获取。

2. LWP (Light Weight Process ID) / TID (Thread ID) - 内核态/系统级别ID

  • 来源:由Linux内核分配和管理。

  • 本质:这是一个pid_t类型的整数,与进程PID属于同一种类型。内核为每一个调度实体(无论是进程还是线程)都分配一个唯一的ID。

  • 作用域系统全局有效。在整个操作系统范围内唯一标识一个调度任务。

  • 用途:用于系统级的监控、调度和调试。toppsperf等工具看到和使用的就是这个ID。

  • 特点

    • 在Linux中,可以通过系统调用gettid()来获取。

    • 主线程的LWP等于进程的PID。

    • 其他线程的LWP是内核分配的新ID。

关键点详解

“pthread_self 得到的这个数实际上是一个地址”

“LWP 得到的是真正的线程ID”

从内核视角看,LWP(由gettid()返回)才是线程的“真实身份”,是调度和资源分配的基本单位。

“主线程和其他线程的栈位置”

对栈位置的描述是Linux线程实现的另一个关键点!

  • 主线程的栈:位于进程虚拟地址空间的栈区域。这个栈是在程序启动时由内核自动设置的,大小通常由系统限制决定(可以用ulimit -s查看)。

  • 其他线程的栈:由pthread库在进程的堆和栈之间的共享区域动态分配。这就是为什么pthread_create可以指定栈大小的原因。

    • 线程栈的分配和管理是pthread库的职责。

    • 当线程退出时,pthread库负责回收这片栈内存。

为什么要设计两层ID?

这种设计体现了优秀的抽象分层思想:

  1. 可移植性:POSIX标准只定义了pthread_t,不关心底层实现。应用程序使用pthread_t可以保证在不同UNIX系统之间的可移植性。

  2. 灵活性:pthread库可以自由选择如何实现和管理线程,比如将TCB结构体放在堆上,并用其地址作为ID。

  3. 效率:用户态的线程操作(如获取自身ID)非常快,无需陷入内核。

  4. 内核简洁性:内核不需要理解复杂的线程库数据结构,它只需要管理好轻量级进程(LWP)的调度即可。

因此:

  • pthread_self()得到的ID是给pthread库用的,用于进程内线程管理。

  • gettid()ps -L看到的LWP是给内核用的,用于系统级任务调度。

  • 两者各司其职,共同构成了Linux强大而灵活的多线程能力。

那既然在内核中,由库来实现和管理线程,那要如何管理起来呢?先描述再组织

pthreads库如何\"先描述,再组织\"地管理线程

pthreads库虽然运行在用户空间,但它通过精巧的数据结构设计和系统调用封装,实现了完整的线程管理功能。

1. \"先描述\" - 定义线程控制块(TCB)

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就是线程的\"身份证\"和\"档案\",完整描述了线程的所有属性和状态。

2. \"再组织\" - 管理所有TCB

pthread库通过以下数据结构组织所有线程的TCB,实现快速访问与调度:

  1. TCB索引表

    • 全局数组 struct pthread *__thread_list[MAX_THREADS]
    • 通过用户级线程ID(pthread_t)作为下标直接定位TCB(#ref1)
    • 示例:TCB = __thread_list[(unsigned long)pthread_self % MAX_THREADS]
  2. LWP ↔ TCB 映射表

    • 哈希表 hash_map
    • 用途:内核通过LWP查询TCB(如处理信号时需修改TCB信号掩码)(#ref2)
  3. 线程状态队列

    队列类型 数据结构 用途 就绪队列 红黑树(按优先级) 用户级调度(配合LWP内核调度) 等待队列 链表 阻塞在条件变量/互斥锁的线程 分离线程回收队列 链表 自动回收已终止的分离线程

3. 与内核的协作

虽然pthreads库在用户空间管理线程,但它需要内核的支持来实现真正的并发执行:

  1. 线程创建:当调用pthread_create()时:

    • 库函数分配TCB结构体和线程栈

    • 初始化TCB中的各种字段

    • 将新TCB添加到全局线程列表中

    • 调用clone()系统调用,请求内核创建真正的执行上下文

  2. 线程调度:虽然pthreads库管理线程状态,但实际的调度决策由内核做出。库需要与内核协作处理线程的阻塞、唤醒等状态转换。

  3. 同步原语:互斥锁、条件变量等同步机制虽然在用户空间实现了一部分优化(如futex),但在需要时仍然会通过系统调用进入内核。

线程退出和清理

当线程结束时,pthreads库需要:

  1. 保存线程返回值到TCB中

  2. 如果线程是joinable的,将其标记为已终止但资源尚未回收

  3. 如果是detached的,立即回收TCB和栈空间

  4. 从全局线程列表中移除该TCB

总结:分层抽象的艺术

pthread库的线程管理是用户态与内核态协作的典范

  1. 描述层
    • 通过TCB结构体封装线程全生命周期状态
    • pthread_t 作为TCB指针提供进程内唯一标识
  2. 组织层
    • 全局索引表实现 O(1) 复杂度访问
    • 队列结构管理不同状态线程
  3. 内核桥接
    • 将POSIX API转化为 clone/futex 等系统调用
    • 维护LWP↔TCB映射保证内核操作可定位用户态资源

3. 线程终止

三种线程终止方法详解

1. 从线程函数 return

这是最自然、最推荐的线程终止方式。

工作原理

  • 当线程执行到其启动函数的 return 语句时,线程会正常结束

  • 返回值可以通过 pthread_join 获取

注意事项

  • 主线程中从 main 函数 return 会终止整个进程

  • 返回的指针必须指向全局数据或堆上分配的内存,不能指向线程栈上的局部变量

2. 调用 pthread_exit 终止自己

这种方式允许线程在任何地方主动终止自己,而不必返回到函数开头。

函数原型

void pthread_exit(void *value_ptr);

使用场景

  • 在线程执行的任何地方需要立即退出

  • 当线程需要返回一个值,但无法通过函数返回实现时

重要注意事项

  1. 内存管理value_ptr 不能指向线程栈上的局部变量,因为线程退出后栈会被销毁

  2. 主线程使用:在主线程中调用 pthread_exit 会终止主线程,但其他线程会继续运行,直到所有线程都结束

  3. 清理处理程序:调用 pthread_exit 会执行线程的清理处理程序(通过 pthread_cleanup_push 注册的)

3. 调用 pthread_cancel 取消另一个线程

这种方式允许一个线程请求终止同一进程中的另一个线程。

函数原型

int pthread_cancel(pthread_t thread);

取消机制的工作原理
线程取消不是立即发生的,而是依赖于目标线程的取消状态和类型:

  1. 取消状态(通过 pthread_setcancelstate 设置):

    • PTHREAD_CANCEL_ENABLE:允许取消(默认)

    • PTHREAD_CANCEL_DISABLE:禁止取消

  2. 取消类型(通过 pthread_setcanceltype 设置):

    • PTHREAD_CANCEL_DEFERRED:延迟取消(默认),只在取消点检查取消请求

    • PTHREAD_CANCEL_ASYNCHRONOUS:异步取消,可以在任何时间点被取消

取消点:一些特定的函数调用会成为取消点,如:

  • sleep()usleep()nanosleep()

  • read()write()open()close()

  • pthread_join()pthread_cond_wait()

  • 等等

关键注意事项总结

  1. 返回值的内存管理

    • 无论是通过 return 还是 pthread_exit 返回的值,都必须指向全局数据或堆上分配的内存

    • 绝对不能指向线程栈上的局部变量,因为线程退出后栈会被销毁

  2. 资源清理

    • 线程终止时,系统会自动释放线程特有的资源(如栈空间)

    • 但线程分配的其他资源(如打开的文件、动态分配的内存等)需要程序员显式清理

    • 可以使用 pthread_cleanup_push 和 pthread_cleanup_pop 注册清理函数

  3. 取消的协作性

    • 线程取消是一种协作机制,目标线程必须配合才能被取消

    • 如果线程禁用取消或从不到达取消点,它将无法被取消

  4. 线程分离

    • 如果线程被设置为分离状态(detached),则不需要其他线程调用 pthread_join 来回收资源

    • 分离线程终止后,系统会自动回收其资源


4. 线程等待

为什么需要线程等待?

  1. 资源泄漏防止

    • 线程退出后,其栈空间和线程控制块(TCB)等资源不会自动释放

    • 这些资源会一直占用进程的地址空间,导致\"僵尸线程\"问题

    • 类似于进程中的僵尸进程,如果不处理,会逐渐耗尽系统资源

  2. 地址空间复用

    • 新创建的线程不会复用已退出线程的地址空间

    • 每次创建新线程都会分配新的栈空间和控制结构

    • 如果不回收旧线程的资源,进程的内存占用会不断增长

  3. 同步需求

    • 主线程可能需要等待工作线程完成特定任务后才能继续执行

    • 线程间需要协调执行顺序,确保数据一致性

  4. 结果获取

    • 工作线程可能需要将执行结果返回给主线程或其他线程

    • pthread_join 是获取线程返回值的标准机制

pthread_join 函数深度解析

函数原型

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 返回的值必须位于全局内存或堆中(禁止指向栈变量)

综合示例:

#include #include #include #include #include #include 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);}

运行结果:

重要注意事项和最佳实践

  1. 线程状态要求

    • 只能对非分离(joinable)状态的线程调用 pthread_join

    • 如果线程处于分离(detached)状态,调用 pthread_join 会失败并返回 EINVAL

  2. 一对一关系

    • 每个线程只能被一个线程 join 一次

    • 多次 join 同一个线程会导致未定义行为

  3. 内存管理责任

    • 通过 pthread_join 获取的返回值内存必须由调用者负责释放

    • 线程不应该返回指向其栈上数据的指针

  4. 错误处理

    • 总是检查 pthread_join 的返回值

    • 常见的错误码:

      • ESRCH:没有找到与给定线程ID对应的线程

      • EINVAL:线程不是可连接状态,或者另一个线程已经在等待此线程

      • EDEADLK:死锁情况,例如线程尝试join自己

  5. 超时处理

    • pthread_join 没有超时机制,会无限期等待

    • 如果需要超时功能,可以考虑使用条件变量或其他同步机制


5. 线程分离

默认情况:可连接线程(Joinable Thread)

  • 新创建的线程默认是可连接的(joinable)

  • 这类线程终止后,必须由其他线程调用 pthread_join 来回收资源

  • 如果不进行 join 操作,线程资源会泄漏,形成\"僵尸线程\"

分离线程(Detached Thread)

  • 分离线程在终止时会自动释放所有资源

  • 不需要也不能被其他线程 join

  • 适用于不需要获取线程返回值的场景

pthread_detach 函数详解

函数原型

int pthread_detach(pthread_t thread);

参数说明

  • thread:要分离的线程ID

返回值

  • 成功返回 0

  • 失败返回错误码(如 ESRCH 表示线程不存在,EINVAL 表示线程已经是分离状态)

使用方式

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 是互斥的

  • 一个线程不能同时是可连接和分离的

  • 如果尝试 join 一个已分离的线程,会返回 EINVAL 错误

  • 如果尝试分离一个已分离的线程,也会返回 EINVAL 错误

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\"); }}

总结

线程分离是多线程编程中的重要概念,它提供了自动资源回收的机制:

  1. 使用场景:适用于不需要获取线程返回值的后台任务、事件处理等场景

  2. 分离方式

    • 其他线程调用 pthread_detach(thread_id)

    • 线程自我分离:pthread_detach(pthread_self())

    • 创建时指定分离属性

  3. 优势

    • 避免资源泄漏

    • 简化代码,不需要显式调用 pthread_join

    • 提高程序的可维护性

  4. 注意事项

    • 分离后不能再 join

    • 仍需负责清理线程分配的非线程特有资源

    • 总是检查分离操作的返回值


6. 线程封装

代码如下:

Thread.hpp:

#pragma once#include #include #include #include #include #include namespace ThreadModlue{ static uint32_t number = 1; // 不是原子型的,先不处理 class Thread { using func_t = std::function; private: void Enabledetach() { std::cout << \"线程被分离了\" << std::endl; _isdetach = true; } void EnableRunning() { _isrunning = true; } // 新线程执行 static void* Routine(void* args) // 属于类内的成员函数,默认包含this指针! { Thread* self = static_cast(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 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_nppthread_getname_np 是两个用于管理线程名称的非标准函数(\"_np\"后缀表示\"non-portable\",即不可移植)。这些函数主要适用于Linux和其他类Unix系统,常用于多线程程序的调试与管理。