Linux 之 【多线程】(pthread_xxx、轻量级进程、原生线程库、线程ID、__thread、线程栈、线程与信号、线程与程序替换)

目录

1.线程的其他相关概念

线程与轻量级进程

原生线程库

线程ID

__thread关键字

栈分配机制对比

线程与信号

线程与程序替换

2.线程管理函数

[pthread_t 类型详解表](#pthread_t 类型详解表)

不同ID的区别

[pthread_create - 创建线程](#pthread_create - 创建线程)

[pthread_join - 等待线程结束](#pthread_join - 等待线程结束)

[pthread_detach - 分离线程](#pthread_detach - 分离线程)

[pthread_exit - 线程退出](#pthread_exit - 线程退出)

退出函数比较

[pthread_cancel - 取消线程](#pthread_cancel - 取消线程)

[pthread_self - 获取当前线程ID](#pthread_self - 获取当前线程ID)

ID比较

[pthread_equal - 比较线程ID](#pthread_equal - 比较线程ID)


1.线程的其他相关概念

线程与轻量级进程

  • 线程是Linux内核调度的基本单位,在底层通过轻量级进程(LWP)实现

| 特性 | 线程 (Thread) | 轻量级进程 (LWP) |
| 概念层级 | 用户级概念,POSIX标准定义 | 内核级概念,Linux实现机制 |
| 实现方式 | Linux中通过轻量级进程实现 | Linux内核中线程的实际形态 |
| 创建方式 | pthread_create() | clone()系统调用(设置CLONE_VM等标志) |
| 资源开销 | 较小(共享大部分资源) | 较小(相比进程)但比纯用户线程大 |

资源隔离 最低(共享地址空间、文件描述符等) 最低(同一进程的LWP共享资源)

原生线程库

复制代码
  用户空间
      ↓ 创建线程(pthread_create)
  pthread库(用户层)
      ↓ 封装为clone()系统调用
  内核空间
      ↓ 创建LWP(设置CLONE_VM等标志)
  调度器调度LWP执行

  用户空间:
    ├── pthread库(共享区加载)
    │   ├── 线程控制块(TCB)管理 ← pthread_t指向这里
    │   ├── 线程栈分配(共享区内)
    │   └── 线程属性管理
    └── 应用程序代码

  内核空间:
    └── LWP调度与执行(通过clone()创建)

内核通过clone()等系统调用来创建任务,根据资源共享程度的不同表现为进程或线程。为了向用户提供符合POSIX标准的线程接口 ,Linux在应用层提供了原生线程库(pthread)

  • pthread 库几乎是任何Linux平台默认自带的,在多线程编程时必须链接使用该库(-lpthread)

    编译时需要链接pthread库

    gcc thread.c -o thread -lpthread

    C++11多线程程序也需要链接

    g++ thread.cpp -o thread -std=c++11 -lpthread

内核仅提供轻量级进程(LWP)这一底层机制,而用户需要符合POSIX标准的、易用的线程接口。pthread库正是为了解决这一鸿沟而存在,它将内核的LWP机制封装成标准化的线程API,管理用户空间的线程控制块(TCB)、线程栈和同步原语,同时隐藏了clone()系统调用的复杂性

线程ID

ID层次 获取方式 本质 特点
用户态线程ID (pthread_t) pthread_self() TCB在共享区中的起始地址 进程内唯一,用户空间标识
内核态线程ID (LWP ID) syscall(SYS_gettid) 内核LWP的PID 系统全局唯一,调度实体
线程组ID (进程PID) getpid() 主线程的LWP ID 所有线程相同,标识进程

__thread关键字

_thread用于定义线程局部存储(Thread-Local Storage, TLS)变量使用__thread修饰的变量,每个线程都拥有其独立的副本,线程间互不干扰主要适用于内置基本类型 (如 int、char、float、指针等)以及用编译时常量初始化的简单结构体不支持动态初始化、动态内存分配或复杂对象。 这些变量的存储空间位于各线程自己的线程控制块(TCB)或专门的TLS段中,生命周期与线程相同,线程结束时自动释放。__thread通常用于存储线程特有的上下文信息,如线程ID、错误状态或线程局部缓存,能有效避免多线程环境下的数据竞争

栈分配机制对比

线程类型 栈位置 分配方式 大小
主线程 进程地址空间栈区 进程启动时分配 由系统限制决定
新线程 共享库映射区 pthread库动态分配 默认8MB,可通过属性设置

每一个线程在调用时都必须拥有独立的栈结构,因为每一个线程都会有自己的调用链,保存运行时的临时变量

线程与信号

  • 在Linux系统中,当向进程(通过PID) 发送信号时,信号被递送给整个线程组,通常由主线程处理或任一线程处理(取决于信号处理设置)。当向特定线程(通过LWP ID) 发送信号时,信号仅递送给该线程,但致命信号(如SIGKILL、SIGTERM、SIGSTOP) 无论发送目标是进程还是线程,都会导致整个进程终止,这是因为这些信号作用于进程级别,内核会终止所有线程。而**非致命信号(如SIGUSR1、SIGUSR2)**发送给特定线程时,只有该线程会响应和处理。因此,虽然可以指定信号发送给特定线程,但对于进程级控制的信号,线程作为进程的执行分支,最终仍体现为进程级别的行为

线程与程序替换

  • 在Linux多线程进程中,任何一个线程调用 exec 系列函数(如 execve, execl, execvp 等)都会导致整个进程的代码和数据被完全替换,所有线程立即终止,只有调用 exec 的线程继续执行新程序

2.线程管理函数

pthread_t 类型详解表

项目 说明
类型定义 typedef unsigned long int pthread_t; (Linux常见实现)
本质 POSIX线程标识符类型,用于唯一标识进程内的一个线程
特性 不透明数据类型 (opaque type),用户不应直接操作其内部值
内部实现 在Linux NPTL实现中,通常是线程控制块(TCB)在共享区的起始地址
大小 sizeof(pthread_t) 通常是8字节(64位系统)或4字节(32位系统)
比较方式 必须使用 pthread_equal(t1, t2) 函数,不能用 == 运算符
获取方式 1. 创建时获取: pthread_create(&tid, ...) 2. 当前线程: pthread_self()
作用范围 进程内有效,不能跨进程识别线程
与内核ID的关系 不同于内核LWP ID (gettid()获取) 和进程PID (getpid()获取)
生命周期 从线程创建开始有效,到线程结束后变为无效(但ID值仍可能被重用)
打印方式 printf("%lu", (unsigned long)tid); 或使用专用格式化函数
用途 1. 线程创建、等待、取消 2. 线程资源管理 3. 线程间标识和区分

不同ID的区别

ID类型 获取函数 作用域 本质 用途
pthread_t pthread_self() 进程内 用户态线程库的线程句柄 线程库API操作
LWP ID syscall(SYS_gettid) 系统全局 内核调度实体ID 系统级线程调度、ps命令查看
进程PID getpid() 系统全局 进程标识符 进程管理、进程间通信
线程组ID getpid() (主线程) 系统全局 主线程的PID 标识整个线程组

使用示例表

操作 代码示例 说明
创建线程 pthread_create(&tid, NULL, func, arg); tid 存储新线程ID
获取自身ID pthread_t myid = pthread_self(); 返回调用线程的ID
比较线程ID if (pthread_equal(id1, id2)) {...} 正确比较方式
错误比较 if (id1 == id2) {...} 错误! 可能工作但不保证
格式化输出 printf("Thread ID: %lu\n", (unsigned long)tid); 转换为数值打印
存储为字符串 char buf[32]; snprintf(buf, sizeof(buf), "%lu", (unsigned long)tid); 便于日志记录

pthread_create - 创建线程

项目 说明
函数原型 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
功能 创建一个新的线程
参数说明 - thread: 指向线程ID的指针,用于存储新线程的ID - attr: 线程属性指针,NULL表示默认属性 - start_routine: 线程函数的地址,格式为void func(void) - arg: 传递给线程函数的参数**
返回值 成功: 0 失败: 返回错误编号(正整数),不设置errno
注意事项 1. 线程创建后立即开始执行 2. 线程函数必须返回void* 3. 主线程应使用pthread_joinpthread_detach处理新线程

pthread_create的最后一个参数传值,不取地址

不使用共同变量传参,因为不知道哪一个线程先调度

pthread_join - 等待线程结束

项目 说明
函数原型 int pthread_join(pthread_t thread, void **retval)
功能 阻塞等待指定线程结束,并回收线程资源
参数说明 - thread: 要等待的线程ID - retval: 接收线程返回值的二级指针,NULL表示不关心返回值
返回值 成功 : 0 失败 : 返回错误编号(正整数),不设置errno
注意事项 1. 线程必须是非分离状态 (joinable) 2. 一个线程只能被一个线程join一次 3. 避免线程僵尸(类似进程僵尸)
  1. 在Linux多线程编程中,线程的调度顺序由操作系统决定
  2. 为避免资源泄漏,必须正确处理线程:使用pthread_join()显式等待 线程结束并回收资源,或使用pthread_detach()设置线程为分离状态使其自动回收
  3. 线程结束后如果既未join也未detach,会导致pthread库分配的资源(线程栈、TCB等)泄漏
  4. 这种泄漏完全无法通过系统命令(如ps)查看或检测,因为线程资源由pthread库在用户空间管理,不反映在内核的进程表中,只能通过内存分析工具或程序监控间接发现

pthread_detach - 分离线程

项目 说明
函数原型 int pthread_detach(pthread_t thread)
功能 将线程设置为分离状态,线程结束时系统自动回收资源
参数说明 thread: 要分离的线程ID
返回值 成功 : 0 失败 : 返回错误编号(正整数),不设置errno
注意事项 1. 分离后不能再用pthread_join等待 2. 资源在线程结束时自动释放 3. 适用于不需要等待结果的线程

pthread_exit - 线程退出

项目 说明
函数原型 void pthread_exit(void *retval)
功能 终止当前线程的执行
参数说明 retval: 线程的返回值,可被pthread_join获取
返回值 无返回值(函数不返回)
注意事项 1. 与return不同,return只能返回线程函数 2. 主线程调用pthread_exit(),主线程终止但其他线程继续运行,直到所有线程结束进程才退出 3. 清理函数会被调用

退出函数比较

调用线程 函数 效果
任何线程 exit(status) 整个进程立即终止,所有线程强制结束
任何线程 pthread_exit(retval) 子线程调用 pthread_exit:仅终止自身。 主线程调用 pthread_exit:主线程终止,但进程挂起等待其他非分离子线程结束后才退出。
主线程 return 主线程 return 等同于 exit(),会直接导致整个进程终止,所有子线程被强制结束

pthread_cancel - 取消线程

项目 说明
函数原型 int pthread_cancel(pthread_t thread)
功能 向指定线程发送取消请求
参数说明 thread: 要取消的线程ID
返回值 成功 : 0 失败 : 返回错误编号(正整数),不设置errno
注意事项 1. 线程在取消点才会响应取消请求(陷入内核的系统调用或特定函数设置) 2. 默认取消类型是延迟取消 3. 线程被取消后,通过pthread_join获取的返回值是PTHREAD_CANCELED宏(定义为((void *)-1)

pthread_self - 获取当前线程ID

项目 说明
函数原型 pthread_t pthread_self(void)
功能 获取调用线程自身的ID
参数说明
返回值 当前线程的ID
注意事项 1. 线程ID只在进程内唯一 2. 可用于线程间的比较(需使用pthread_equal) 3**. 主线程的ID不一定等于进程ID**

ID比较

ID类型 获取函数 说明 主线程的情况
pthread_t (用户态线程ID) pthread_self() pthread库分配的线程标识符 不等于进程PID
LWP ID (内核线程ID) syscall(SYS_gettid) 内核调度的轻量级进程ID 等于进程PID
进程PID (线程组ID) getpid() 进程标识符,所有线程相同 等于主线程LWP ID

pthread_equal - 比较线程ID

项目 说明
函数原型 int pthread_equal(pthread_t t1, pthread_t t2)
功能 比较两个线程ID是否相等
参数说明 t1, t2: 要比较的两个线程ID
返回值 相等: 非0值(通常是1) 不相等: 0
注意事项 1. pthread_t是不透明类型,不能用==比较 2. 通常在进程内比较有效
相关推荐
choke23312 小时前
Python 基础语法精讲:数据类型、运算符与输入输出
java·linux·服务器
AZ996ZA12 小时前
自学linux的第二十一天【DHCP 服务从入门到实战】
linux·运维·服务器·php
_OP_CHEN12 小时前
【Linux系统编程】(二十八)深入 ELF 文件原理:从目标文件到程序加载的完整揭秘
linux·操作系统·编译·c/c++·目标文件·elf文件
Fleshy数模13 小时前
MySQL 表创建全攻略:Navicat 图形化与 Xshell 命令行双模式实践
linux·mysql
神梦流13 小时前
GE 引擎的非标准数据流处理:稀疏张量与自定义算子在图优化中的语义保持
linux·运维·服务器
.小墨迹14 小时前
apollo学习之借道超车的速度规划
linux·c++·学习·算法·ubuntu
Lsir10110_14 小时前
【Linux】中断 —— 操作系统的运行基石
linux·运维·嵌入式硬件
Sheffield14 小时前
command和shell模块到底区别在哪?
linux·云计算·ansible
历程里程碑14 小时前
Linux20 : IO
linux·c语言·开发语言·数据结构·c++·算法