序言
在不同的系统中,为了更好地管理用户可能会采取不同的编号。比如在学校的教务系统中,管理学生使用的是学号;但是在住宿系统中,为了更加方便的获取一个学生的寝室信息,可能会采取结合你是哪一栋,哪一层,哪一间,哪一个铺位号的信息来作为新的的编号。
有很多在我们计算机系统体系的设定,其实利用生活的道理都能非常直观的解释。本篇文章的内容需要一些线程的前置知识,可以点击此处查看哦 点击跳转👈 。
1. 引出问题
在 Linux 系统中,我们可以通过指令 ps -a
来查看当前用户下的启动程序的进程的 PID
,同时我们在代码中使用函数 getpid()
函数也可以获取当前进程的 PID
,举个栗子:
1 Test.cc X
1 #include <iostream>
2 #include <unistd.h>
3
4 // 一段非常简单的程序
5 // 每隔两秒打印当前进程的PID
6
7 int main()
8 {
9 while(true)
10 {
11 std::cout << "Hello, my pid is " << getpid() << "." << std::endl;
12 sleep(2);
13 }
14
15 return 0;
16 }
启动这个程序之后,我们再使用该指令来获取当前进程的信息:
可以很明显的看到,两者的 PID
是一样的。
现在,我们来回忆一下线程的部分知识点:
- 线程是进程中的一个实体,是
CPU 调度和分派的基本单位
,它是进程中的实际运作单位
- Linux 系统上,并不存在真正意义上的线程,他的线程是通过
模拟进程来实现的
,被称为轻量级进程(LWP)
。
我们可以通过指令 ps -aL
查看当前用户下的线程消息,运行上段程序:
可以看到,当前程序我们并没有显示的创建副线程,但是还是存在相应线程信息,说明一个进程默认是有一个线程的(称为 主线程
)。
我们对上段程序进行一点改造,创建一个副线程执行其他任务:
#include <iostream>
#include <unistd.h>
#include <pthread.h>
void *thread_Func(void *arg)
{
while(true)
{
std::cout << "Hello, I am pthread_1, my tid is " << pthread_self() << "." << std::endl;
sleep(2);
}
return nullptr;
}
int main()
{
pthread_t tid = 0;
pthread_create(&tid, nullptr, thread_Func, nullptr);
while(true)
{
std::cout << "Hello, I am main pthread, my tid is " << pthread_self() << "." << std::endl;
sleep(2);
}
pthread_join(tid, nullptr);
return 0;
}
创建了一个父线程和主线程分别执行任务,现在再使用指令 ps -aL
:
通过打印出的结果,我们可以得出一下信息:
- 主线程的
LWP
和该进程的PID
是相同的 - 一个进程包含的线程的
LWP
是连续的 - 线程的
LWP
和TID
并不相同
前面铺垫了这么多内容,终于是引出了疑问,我们进程使用指令和函数得出的 PID
是一个值,为什么线程的 LWP
和通过函数 pthread_self()
函数得出的值不一样呢?
2. 底层设计
2.1 LWP
LWP 轻量级进程
是在 Linux 系统下设计线程的方式,线程和进程一样也是需要管理的,所以为了更好地管理所有线程,系统存在一个结构体存储线程地相关信息,而 LWP
对应的值就是该线程对应结构体唯一的标识符。
2.2 TID
那 TID
又代表什么呢?TID 代表一个地址!
首先我们需要了解,Linux 的线程又叫做 轻量级进程
,所以所维护的线程信息都是针对进程的角度,所以光靠这些信息是不够维护一个线程的。
但是大家不要忘记我们使用了一个库 POSIX 线程库
,该库除了为我们提供相关的线程使用方法,还帮我们维护线程所特有的属性(线程栈,线程局部存储等等)。
现在深入内容之前,我们有必要简单回顾一下加载动态库到进程地址空间:
- 当程序使用到一个动态库时,会先查看他是否已经被加载到内存中了,如果没有,则先会加载到内存中
- 其次,会将该库映射到进程地址空间的共享区上,通过页表建立虚拟地址和物理地址的映射关系
- 最后,我们就可以调用共享库的函数了
当我们使用库函数 pthread_create()
创建一个线程时:
会在该库所在的区域为我们申请一个内存块,该块中存放对应线程的信息数据,该函数不是有一个返回型参数 tid
吗,该参数就是 线程对应内存块的地址!
所以,我们可以使用该 TID
来控制对应线程!
3. 解决问题
所以,通过基本了解了底层的情况,现在我们在来回答这个问题,LWP
是线程在内核中的结构,他被视为一个轻量级进程;TID
是线程在库中的结构,他包含线程所特有的数据和属性。
4. 总结
在这篇文章中介绍了线程 ID
和 LWP
,以及造成不同的原因,以及底层细节,希望大家有所收获。