[Linux] 如何理解线程ID?什么是线程局部存储?
线程ID
如何理解线程ID
Linux线程ID
pthread_create()
创建线程的时候, 第一个参数就是用来接收线程ID的.此线程ID,
是 pthread 库维护线程时所使用的唯一标识符
而不是 Linux系统内核 中对于表示线程的PCB 的线程ID. Linux内核中的线程ID, 就是PCB的pid, 是LWP.
Linux内核中的LWP 与 pthread 库维护的线程ID, 是 1对1的关系.
pthread
创建一个线程, 操作系统就会对新的PCB 分配一个 LWP,pthread
库也会分配一个线程ID 作为库维护线程的唯一标识符
什么是线程ID
线程在操作系统中的唯一标识符
. 一般为 无符号的长整型(pthread_t)
.#include <iostream>
#include <pthread.h>
using std::cout;
using std::endl;
int main() {
pthread_t tid1;
pthread_create(&tid1, nullptr, nullptr, nullptr);
cout << "tid1 = " << tid1 << endl;
return 0;
}
实际上线程ID表示的是一个地址
, 如果我们将 获取到的TID以16进制输出:如何理解线程ID **
唯一标识
之外. 实际还 表示一个地址
.pthread_create()
创建的线程, 在运行时, 一定会产生一些临时数据:临时变量、函数调用等
新线程的栈是独立与主线程(进程)的
.线程的管理 **
pthread
库的接口, 编译生成的可执行程序. 运行时肯定是需要 libpthread.so
动态库的pthread
库, 是用户级的线程库, 程序运行调用接口时, 会被 加载到内存
中, 再 映射到进程地址空间的共享区
pthread
库中的接口时, 操作系统就会将磁盘中的动态库加载到内存中, 然后线程就会跳到共享区去找内存加载的动态库代码.进程中的代码一定包含三部分:
- 程序编写的代码
- 动态库代码
- 系统内核代码
pthread线程库
会被加载到内存中并被 映射到进程地址空间的共享区
pthread线程库
的进程抽象为这样:线程 是操作系统内核来管理的吗?
并不是
.Linux 内核只有轻量级进程的概念、执行流的概念
. 即使可以模拟出线程, 但是线程也不是Linux内核中存在的概念.提供了
创建 子进程、共用进程地址空间进程的接口, 而并 没有提供
直接创建、管理线程的接口.pthread
库 封装了系统关于创建子进程相关接口 而成的库接口.pthread
库中 帮我们实现了从轻量级进程到线程的过程:创建线程栈、分配任务, 以及线程的控制等相关接口.pthread
库管理的.pthread
库则通过轻量级进程和操作系统提供的接口 实现了我们理解的线程
pthread
库代码创建的. 那么, 为了方便线程的管理 pthread
库代码中也实现了 有关描述线程属性的结构体
.pthread
库中实现的描述线程属性的结构体 是struct thread_struct{}
, 那么此结构体的成员就可以抽象为:struct thread_struct {
pthread_t tid; // 线程ID
void* stack; // 线程栈
……
}
pthread
库维护有线程的栈、线程的分配等结构. 此结构体也是库维护的:pthread
库创建并维护
图示的意思是, pthread 库会创建并维护 线程属性的结构体, 而不是说此结构体会存储在库所在代码空间中, 更不是会存储在共享区.
-
线程的栈是由谁创建的?我们使用
pthread_create()
创建线程, 那么 线程的栈就是就是pthread
库创建的. 因为pthread
库在创建线程的时候, 会创建线程的属性结构体, 结构体内维护由线程栈的信息.这里介绍一个内容:
Linux若使用
pthread
库创建线程, 则进程地址空间内的栈区
就是主线程的栈区
. 其他新线程的栈区
是在 由pthread库维护的一块空间内 分配维护的
. -
我们通过
pthread
库获取的线程ID, 表示的地址实际就是一个 描述线程属性的结构体的地址.pthread_t
到底是什么类型呢?取决于实现
. 对于Linux目前实现的NPTL
实现而言,pthread_t
是一个 无符号长整型, 可以用来表示pthread
库 维护线程时的唯一标识符, 也可以用来表示进程地址空间中的一个地址. 此地址就是 描述线程属性的结构体的地址.
pthread 库实现的线程是在Linux轻量级进程的基础上, 又维护了一些属性实现的.
即 pthread 库中描述线程属性的结构体应该是直接或间接维护有线程的PCB.
对于Linux目前实现的
NPTL
实现而言,pthread_t
是一个 无符号长整型, 可以用来表示进程地址空间中的一个地址.但这种实现方式并不是 POSIX 标准规定的. 所以是不符合 POSIX 标准的. 所以最好不要通过 此线程ID 操作线程的属性. 不然可能会影响代码的可移植性.
线程局部存储
pthread
库中定义的描述线程的属性的结构体中, 维护有一个特殊区域:线程局部存储区域
#include <iostream>
#include <unistd.h>
#include <syscall.h>
#include <pthread.h>
using std::cout;
using std::endl;
int global_value = 100;
void* startRoutine(void* args) {
const char* name = static_cast<const char*>(args);
while (true) {
printf("%s: %lu global_value: %d &global_value: %p Inc: %d lwp: %ld\n",
name, pthread_self(), global_value, &global_value, --global_value, ::syscall(SYS_gettid));
sleep(1);
}
return nullptr;
}
int main() {
pthread_t tid1, tid2, tid3;
pthread_create(&tid1, nullptr, startRoutine, (void*)"thread1");
pthread_create(&tid2, nullptr, startRoutine, (void*)"thread2");
pthread_create(&tid3, nullptr, startRoutine, (void*)"thread3");
pthread_join(tid1, nullptr);
pthread_join(tid2, nullptr);
pthread_join(tid3, nullptr);
return 0;
}
--
操作, 并且可以看到, 不同线程访问的global_value地址都是相同的 会互相影响.__thread
:__thread int global_value = 100;
不同线程看到的是不同的地址, 实际看到的是不同的数据
.线程局部存储
, 只属于线程自己的局部存储数据
作者: 哈米d1ch 发表日期:2023 年 4 月 15 日