当前位置: 首页 > ai >正文

【Linux系统】线程控制

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. 线程创建

函数原型与核心机制

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

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

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

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

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

4. void *arg - 线程参数

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

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

  • 注意事项

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

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

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

返回值与错误处理

  • 成功:返回 0

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

  • 常见错误码

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

    • EINVALattr 参数无效

    • 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 ; donePID     LWP TTY          TIME CMDPID     LWP TTY          TIME CMDPID     LWP TTY          TIME CMD221797  221797 pts/4    00:00:00 test221797  221798 pts/4    00:00:00 testPID     LWP TTY          TIME CMD221797  221797 pts/4    00:00:00 test221797  221798 pts/4    00:00:00 testPID     LWP TTY          TIME CMD221797  221797 pts/4    00:00:00 test221797  221798 pts/4    00:00:00 testPID     LWP TTY          TIME CMD221797  221797 pts/4    00:00:00 test221797  221798 pts/4    00:00:00 testPID     LWP TTY          TIME CMD221797  221797 pts/4    00:00:00 test221797  221798 pts/4    00:00:00 testPID     LWP TTY          TIME CMD221797  221797 pts/4    00:00:00 testPID     LWP TTY          TIME CMD221797  221797 pts/4    00:00:00 testPID     LWP TTY          TIME CMD221797  221797 pts/4    00:00:00 testPID     LWP TTY          TIME CMD221797  221797 pts/4    00:00:00 testPID     LWP TTY          TIME CMD221797  221797 pts/4    00:00:00 testPID     LWP TTY          TIME CMDPID     LWP TTY          TIME CMDPID     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.h>
pthread_t pthread_self(void);
  • 参数:无
  • 返回值pthread_t 类型,表示当前线程的唯一标识符
  • 错误码:永不失败(总是成功)

线程 ID (pthread_t) 的本质

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

示例:

#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_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<pid_t LWP, struct pthread* TCB>
    • 用途:内核通过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 <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 returnpthread_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 exitpthread_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 otherpthread_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);elseprintf("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 <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_nppthread_getname_np 是两个用于管理线程名称的非标准函数("_np"后缀表示"non-portable",即不可移植)。这些函数主要适用于Linux和其他类Unix系统,常用于多线程程序的调试与管理。


http://www.xdnf.cn/news/18946.html

相关文章:

  • 安装Docker Desktop报错WSL needs updating
  • AAA服务器
  • VS2022+QT6.7+NetWork(TCP服务器多客户端助手)
  • 【若依】RuoYi-Vue-springboot3分离版
  • 专业的储存数据的结构:数据库
  • (笔记)Android ANR检测机制深度分析
  • 第1记 cutlass examples 00 的认真调试分析
  • Ubuntu 22.04 安装 向日葵远程Client端
  • 并发编程——06 JUC并发同步工具类的应用实战
  • sr04模块总结
  • Scala面试题及详细答案100道(41-50)-- 模式匹配
  • MySQL底层数据结构与算法浅析
  • 捡捡java——2、基础05
  • 部署2.516.2版本的jenkins,同时适配jdk8
  • 【Windows】netstat命令解析及端口状态解释
  • React过渡更新:优化渲染性能的秘密
  • Vue3组件加载顺序
  • MySQL 索引
  • THM Whats Your Name WP
  • SDK、JDK、JRE、JVM的区别
  • python使用sqlcipher4对sqlite数据库加密
  • Mip-splatting
  • GCC版本和C语言标准版本的对应关系
  • java去图片水印的方法
  • 生产环境Vue组件报错:Cannot access before initialization
  • 使用qianjkun uniapp 主应用 集成 vue微应用
  • 8.28作业
  • 可改善能源利用水平、削减碳排放总量,并为可再生能源规模化发展提供有力支撑的智慧能源开源了
  • Python Imaging Library (PIL) 全面指南:Python Imaging Library (PIL)基础图像处理入门
  • 【图像处理基石】DCT在图像处理中的应用及实现