Linux 线程ID
在没有谈到线程前,我们认为一个进程对应的是一个进程描述符PCB,对应一个进程ID。但现在我们引入了线程的概念后,一个用户进程可以包含多个用户态线程,每个线程作为一个独立的调度实体在内核态都有自己的进程描述符PCB,因此Linux内核为了处理以上关系,引入线程组的概念。
我们之前在学习进程的过程中,学过一个函数getpid,作用是获得当前进程的ID,同样,也有一个函数gettid可以获得线程ID,我们来了解这个函数。
查看线程ID:
介绍几个重要参数
PID:当前进程ID
PPID:当前进程的父进程ID
LWP:线程ID
NLWP:线程组内线程的个数
可以看出当前进程是多线程的,进程ID为2715,进程内有2个线程,线程ID分别为2715、2716,其中线程ID和进程ID一致的可以认为是主线程。所有进程的父进程都是bash。但是注意一点,线程是对等的,没有类似于进程中的父进程的概念。
同一个线程组的线程,没有层次关系。
关于线程ID获取的几个方法我们做以下的测试:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/syscall.h>
void* PrintPid(void* param)//子线程函数,用于打印子线程的资源
{
printf("child getpid()=%d,syscall(__NR_gettid)=%ld,\
pthread_self()=%ld\n",getpid(),\
(long int)syscall(__NR_gettid),pthread_self());
}
int main()
{
//打印主线程的资源
printf("main getpid()=%ld,syscall(__NR_gettid)=%ld,\
pthread_self()=%ld\n",getpid(),\
(long int)syscall(__NR_gettid),pthread_self());
pthread_t tid = 0;
//创建子线程
int ret = pthread_create(&tid, NULL, PrintPid, NULL);
if(ret == 0) {
printf("child tid=%ld\n",tid);
}else{
printf("create PrintPid is failed!\n");
}
while(1)
{
sleep(5);
}
return 0;
}
编译后,我们看一下执行结果:
从运行结果我们可以看出,主线程和子线程调用的getpid返回值相同,syscall(__NR_gettid)的系统调用的结果不同,pthread_self的调用结果也不同。但子线程下的pthread_self返回值和主线程的tid值是相同的。那这是什么原因呢?为什么会得到的线程ID会出现不一致呢?
我们来解释一下:
从getpid函数说起,函数原型是pid_t getpid(void)。
该函数返回的是当前进程的进程ID,即pid(int)型的进程识别码,通常情况下,不论该函数在哪一个线程下执行,都获取的是当前主线程的线程ID,同时也是当前进程ID。
syscall(__NR_gettid)系统调用,同syscall(SYS_gettid)。
该函数用于获取当前线程的线程ID,该系统调用底层使用的是gettid函数,而该函数在glibc下是不提供的。
pthread_self函数为获取当前线程自身的ID,返回值和pthread_create函数调用返回的pthread_t的值是相同的,即子线程下的pthread_self返回值和主线程的tid是相同的。
那么,syscall(__NR_gettid)和pthread_self()的返回值都是线程ID,但结果为什么会不同?
线程库其实由两部分组成:内核的线程支持、用户的线程支持(glibc),Linux早期内核不支持线程时,glibc就在库中(用户态)以纤程(用户态线程)的方式支持多线程了,POSIX thread只要求了用户编程的调用接口对内核接口没有要求。Linux下线程的实现就是在内核支持的基础上以POSIX thread的方式对外封装了接口,所以才会有两个ID。