在Linux的top和ps命令中,默认看到最多的是pid (process ID),也许你也能看到lwp (thread ID)和tgid (thread group ID for the thread group leader)等等,而在Linux库函数和系统调用里也许你注意到了pthread id和tid等等。还有更多的ID,比如pgrp (process group ID), sid (session ID for the session leader)和 tpgid (tty process group ID for the process group leader)。概念太多可能很晕,但是只要对Linux的进程和线程的基本概念有准确的理解,这些ID的含义都迎刃而解。下面将介绍进程和线程的核心概念,并以一个示例程序来验证这些ID之间的关系。
Linux的进程和线程
Linux的进程和线程有很多异同点,可以Google下。但只要能清楚地理解一下几点,则足够理解Linux中各种ID的含义。
- 进程是资源分配的基本单位,线程是调度的基本单位
- 进程是资源的集合,这些资源包括内存地址空间,文件描述符等等,一个进程中的多个线程共享这些资源。
- CPU对任务进行调度时,可调度的基本单位 (dispatchable entity)是线程。如果一个进程中没有其他线程,可以理解成这个进程中只有一个主线程,这个主进程独享进程中的所有资源。
- 进程的个体间是完全独立的,而线程间是彼此依存,并且共享资源。多进程环境中,任何一个进程的终止,不会影响到其他非子进程。而多线程环境中,父线程终止,全部子线程被迫终止(没有了资源)。
上述第一点说明是最基础的,也是最重要的。
初步理解各种ID。基本上按照重要程度从高到低,在分割线下方的IDs不太重要。
- pid: 进程ID。
- lwp: 线程ID。在用户态的命令(比如ps)中常用的显示方式。
- tid: 线程ID,等于lwp。tid在系统提供的接口函数中更常用,比如syscall(SYS_gettid)和syscall(__NR_gettid)。
- tgid: 线程组ID,也就是线程组leader的进程ID,等于pid。
- ------分割线------
- pgid: 进程组ID,也就是进程组leader的进程ID。
- pthread id: pthread库提供的ID,生效范围不在系统级别,可以忽略。
- sid: session ID for the session leader。
- tpgid: tty process group ID for the process group leader。
从上面的列表看出,各种ID最后都归结到pid和lwp(tid)上。所以理解各种ID,最终归结为理解pid和lwp(tid)的联系和区别。
下面的图是一张描述父子进程,线程之间关系的图。
上图很好地描述了用户视角(user view)和内核视角(kernel view)看到线程的差别:
- 从用户视角出发,在pid 42中产生的tid 44线程,属于tgid(线程组leader的进程ID) 42。甚至用ps和top的默认参数,你都无法看到tid 44线程。
- 从内核视角出发,tid 42和tid 44是独立的调度单元,可以把他们视为"pid 42"和"pid 44"。
需要指出的是,有时候在Linux中进程和线程的区分也是不是十分严格的。即使线程和进程混用,pid和tid混用,根据上下文,还是可以清楚地区分对方想要表达的意思。上图中,从内核视角出发看到了pid 44,是从调度单元的角度出发,但是在top或ps命令中,你是绝对找不到一个pid为44的进程的,只能看到一个lwp(tid)为44的线程。
理解pid和lwp(tid)的示例程序
下面利用一个示例程序来进一步理解pid和lwp(tid),以及利用格式化的ps命令打印出各种ID。下面的程序在main函数中创建了2个子线程,加上main函数这个主线程,一共有3个线程。在3个线程中分别打印pthread id, pid和lwp(tid),来验证pid和lwp(tid)的关系。
1 #include <unistd.h> 2 #include <sys/syscall.h> 3 #include <stdio.h> 4 #include <pthread.h> 5 6 #define gettidv1() syscall(__NR_gettid) // new form 7 #define gettidv2() syscall(SYS_gettid) // traditional form 8 9 void *ThreadFunc1() 10 { 11 printf("the pthread_1 id is %ld ", pthread_self()); 12 printf("the thread_1's Pid is %d ", getpid()); 13 printf("The LWPID/tid of thread_1 is: %ld ", (long int)gettidv1()); 14 pause(); 15 16 return 0; 17 } 18 19 void *ThreadFunc2() 20 { 21 printf("the pthread_2 id is %ld ", pthread_self()); 22 printf("the thread_2's Pid is %d ", getpid()); 23 printf("The LWPID/tid of thread_2 is: %ld ", (long int)gettidv1()); 24 pause(); 25 26 return 0; 27 } 28 29 int main(int argc, char *argv[]) 30 { 31 pid_t tid; 32 pthread_t pthread_id; 33 34 printf("the master thread's pthread id is %ld ", pthread_self()); 35 printf("the master thread's Pid is %d ", getpid()); 36 printf("The LWPID of master thread is: %ld ", (long int)gettidv1()); 37 38 // 创建2个线程 39 pthread_create(&pthread_id, NULL, ThreadFunc2, NULL); 40 pthread_create(&pthread_id, NULL, ThreadFunc1, NULL); 41 pause(); 42 43 return 0; 44 }
注意编译的时候要利用-l指定library参数。
# gcc threadTest.c -o threadTest -l pthread
执行程序,结果如下:
# ./threadTest the master thread's pthread id is 140154481125184 the master thread's Pid is 20992 The LWPID of master thread is: 20992 the pthread_1 id is 140154464352000 the thread_1's Pid is 20992 The LWPID/tid of thread_1 is: 20994 the pthread_2 id is 140154472744704 the thread_2's Pid is 20992 The LWPID/tid of thread_2 is: 20993
上述结果说明pthread id是pthread库提供的ID,在系统级别没有意义。pid都是线程组leader的进程ID,即20992。而lwp(tid)则是线程ID,分别是20993和20994。
同时利用ps来查看结果,注意ps默认只打印进程级别信息,需要用-L选项来查看线程基本信息。
# ps -eo pid,tid,lwp,tgid,pgrp,sid,tpgid,args -L | awk '{if(NR==1) print $0; if($8~/threadTest/) print $0}' PID TID LWP TGID PGRP SID TPGID COMMAND 20992 20992 20992 20992 20992 30481 20992 ./threadTest 20992 20993 20993 20992 20992 30481 20992 ./threadTest 20992 20994 20994 20992 20992 30481 20992 ./threadTest
从上述结果中可以看到:
- PID=TGID: 20992
- TID=LWP: 20993 or 20994
- 至于SID,30481是bash shell的进程ID。
Linux用户态命令查看线程
top
默认top显示的是task数量,即进程。
可以利用敲"H",来切换成线程。如下,可以看到实际上有96个线程。也可以直接利用top -H命令来直接打印线程情况。
ps
ps的-L选项可以看到线程,通常能打印出LWP和NLWP相关信息。如下命令即可查看线程信息:
ps -eLf
pidstat
pidstat -t [-p pid号] 可以打印出线程之间的关系。
htop
要在htop中启用线程查看,开启htop,然后按<F2>来进入htop的设置菜单。选择“设置”栏下面的“显示选项”,然后开启“树状视图”和“显示自定义线程名”选项。按<F10>退出设置。
注:MAC的F2按fn+F2。