进程ID和线程ID
1. 内核标识的线程ID-LWP,在系统级别有效
在Linux下,目前的线程实现是Native POSIX Thread Libaray,简称NPTL。在这种实现下,线程又被称为轻量级进程。每一个用户态的线程,在内核中都对应一个调度实体,也拥有自己的进程描述符(task_struct结构体)。
没有线程之前,一个进程对应内核中一个进程描述符,对应一个进程ID。但是引入线程的概念之后,一个用户进程下管辖N个用户态线程。每个线程作为一个独立的调度的实体,在内核态中都有自己的进程描述符,进程和内核描述符一下变成了1:N的关系。POSIX标准又要求同一进程中的线程调用getpid函数时返回相同的进程ID,如何解决上述问题呢?
Linux内核就引入了线程组的概念:
struct task_struct { ... pid_t pid; pid_t tgid; ... struct task_struct* group_leader; ... struct list_head thread_group; ... }
多线程的进程,又被称为
线程组。线程组内每一个线程在内核中都有一个进程描述符(task_sruct)与之对应,且进程描述符结构体中的pid对应的是线程id,而非进程id。进程描述符中的tgid含义是Thread Group ID,对应的是用户级的进程id。
现在介绍的线程ID,不同于pthread_t类型的线程ID,和进程ID一样,线程ID是pid_t类型的变量,而且是用来唯一标识线程的一个整型变量。如何看一个线程的ID呢?
如上图所示,我们查看了thread_id进程中的线程相关信息。(最后一行不是)ps命令中的-L选项,会显示如下信息:
(1)LWP:线程ID,即getttid( )系统调用的返回值;
(2)NLWP:线程组内线程的个数
可以看出来,thread_id进程是多线程的,进程ID是6538,进程内有两个线程,线程ID分别是6538、6539。
Linux提供了gettid系统调用来返回其线程ID,可是glibc并没有将该系统调用封装起来,在开放接口来供程序员使用,如果确实需要获得该线程ID,可采用以下方法:
#include <sys/syscall.h> pid_t tid; tid = syscall(SYS_gettid);
我们用以下方法测试如下代码的线程ID:
//所有线程的pid都是进程的pid,均相同 #include <stdio.h> #include <pthread.h> #include <unistd.h> #include <sys/syscall.h> void* thread_run(void* arg)//传入线程创建的函数 { pid_t tid = syscall(SYS_gettid); const char* msg = (const char*)arg; printf("%s: %d\n", msg, tid); sleep(5); pthread_exit((void*)0); } int main() { pthread_t tid1, tid2, tid3;//无符号长整型 pthread_create(&tid1, NULL, thread_run, "thread 1"); pthread_create(&tid2, NULL, thread_run, "thread 2"); pthread_create(&tid3, NULL, thread_run, "thread 3"); pthread_join(tid1, NULL);//不关心线程退出信息 pthread_join(tid2, NULL); pthread_join(tid3, NULL); printf("main thread: tid:%x, pid:%d\n", pthread_self(), getpid()); sleep(5); return 0; }
运行结果为:
可以看到该进程中有一个主线程、三个新线程,打印出来的结果也是四个线程,线程ID分别为6825、6826、6827、6828。结果与我们上面用ps命令查看得到的线程ID一样,所以这里打印的线程ID即为LWP,在内核有效。
6825标识的即为主线程,在内核中被叫做group leader,内核在创建第一个线程时,会将线程组的ID值设置为第一个线程的线程ID,group leader的指针指向自身,即主线程的进程描述符,
所以线程组内存在一个线程ID与进程ID相等,该线程即为线程组的主线程。
线程组内的其他线程ID则由内核分配,但是线程组ID和主线程的线程组ID一样,无论是主线程创建新线程,还是新线程再创建新线程,都是这样。
但是,线程与 进程不一样,进程间有父子关系,但是线程之间均为对等关系,即
同一个线程组的线程,没有层次关系。
2. 用户级别的线程ID
pthread_create函数会产生一个线程ID,存放在第一个函数所指向的地址中,这里的线程ID即为用户级别的线程ID。上面说的ID属于进程调度的范畴,因为线程属于轻量级线程,是操作系统调度的最小单位,所以需要一个数值来唯一标识该线程。而pthread_create产生并标记在第一个参数指向的地址中的线程ID,属于NPTL线程库的范畴。线程库的后序操作,就是根据该线程ID来操作线程的,它仅在当前库内有效。
线程库NPTL提供了pthread_self函数,可获得线程自身的ID:
pthread_t pthread_self(void);对于Linux目前实现的NPTL实现来讲, pthread_t类型的线程ID,本质上是进程地址空间上的一个地址。
我们利用pthread_self编写代码查看用户级别线程的ID:
//所有线程的pid都是进程的pid,均相同 #include <stdio.h> #include <pthread.h> #include <unistd.h> #include <sys/syscall.h> void* thread_run(void* arg)//传入线程创建的函数 { const char* msg = (const char*)arg; //这里打印出tid是地址的形式,是用户级的线程id,只在当前库内有效 printf("%s: tid:%#x, pid:%d\n", msg, pthread_self(), getpid()); sleep(5); pthread_exit((void*)0); } int main() { pthread_t tid1, tid2, tid3;//无符号长整型 pthread_create(&tid1, NULL, thread_run, "thread 1"); pthread_create(&tid2, NULL, thread_run, "thread 2"); pthread_create(&tid3, NULL, thread_run, "thread 3"); pthread_join(tid1, NULL);//不关心线程退出信息 pthread_join(tid2, NULL); pthread_join(tid3, NULL); printf("main thread: tid:%x, pid:%d\n", pthread_self(), getpid()); sleep(5); return 0; }
运行结果为: