摘自《Linux 环境编程:从应用到内核》
在 Linux 中,目前的线程实现是 Native POSIX Thread Library,简称 NPTL。在这种实现下,线程又被称为轻量级进程(Light Weighted Process),每一个用户态的线程,在内核中都有一个调度实体,也拥有自己的进程描述符。
对于进程,可以使用下面的系统调用,获取进程 ID
pid_t getpid(void);
如:
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <unistd.h> 4 5 int main() 6 { 7 pid_t pid = getpid(); 8 printf("pid = %d ", pid); 9 while(1); 10 return 0; 11 }
打印:
jingyg@jingyg:~/share/mytest/linux_userspace/0$ gcc -g -Wall 0_1_hello_world.c -o hello_world jingyg@jingyg:~/share/mytest/linux_userspace/0$ ./hello_world pid = 3929
使用 ps 命令查看进程 ID,(加 -L 选项可以显示线程信息,LWP:线程 ID(调用 gettid()系统调用的返回值),NLWP:线程组内的线程个数)
jingyg@jingyg:~$ ps -efL UID PID PPID LWP C NLWP STIME TTY TIME CMD jingyg 3929 3436 3929 99 1 03:07 pts/1 00:01:24 ./hello_world
可以看到 PID 和 LWP 的值一样。
多线程的进程,又被称作线程组,线程组内的第一个线程,在用户态被称作主线程(main thread)。内核在创建第一个线程时,会将线程组 ID (即进程 ID) 的值设置为第一个线程的线程 ID。
虽然 Linux 提供了 gettid 系统调用来返回线程 ID,但是可惜的是 glibc 并没有将该系统调用封装并开放给程序员使用。如果确实需要获取线程 ID,可以采用如下方法:
#include <sys/syscall.h>
int TID = syscall(SYS_gettid);
如
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <unistd.h> 4 #include <sys/syscall.h> 5 6 int main() 7 { 8 pid_t pid = getpid(); 9 printf("pid = %d ", pid); 10 11 int tid = syscall(SYS_gettid); 12 printf("tid = %d ", tid); 13 return 0; 14 }
打印:
jingyg@jingyg:~/share/mytest/linux_userspace/0$ ./hello_world pid = 3995 tid = 3995
pthread_self()
pthread 库中有一个 pthread_self() 接口用来获取线程 ID,但是这个 ID并不是内核中那个线程 ID
#include <pthread.h> pthread_t pthread_self(void);
pthread_t 到底是个什么样的数据结构呢?因为 POSIX 标准并没有限制 pthread_t 的数据类型,所以该类型取决于具体实现。对于 Linux 目前使用的 NPTL 实现而言,pthread_t 类型的线程 ID,本质上就是一个进程地址空间上的一个地址,而且 pthread_t 类型的线程 ID很有可能被复用:
1 #include <stdio.h> 2 #include <pthread.h> 3 4 void* thread_work(void* param) 5 { 6 printf("pthread_self : %p ", (void*)pthread_self()); 7 return NULL; 8 } 9 10 int main() 11 { 12 pthread_t tid = 0; 13 int ret = pthread_create(&tid, NULL, thread_work, NULL); 14 ret = pthread_join(tid, NULL); 15 16 ret = pthread_create(&tid, NULL, thread_work, NULL); 17 ret = pthread_join(tid, NULL); 18 return 0; 19 }
打印:(编译 pthread 相关接口时,需要加上 -lpthread 选项,如 gcc -g -Wall 0_1_hello_world.c -o hello_world -lpthread)
jingyg@jingyg:~/share/mytest/linux_userspace/0$ ./hello_world pthread_self : 0xb7525b40 pthread_self : 0xb7525b40
如果线程退出了,重新创建的线程很可能复用同一个 pthread_t 类型的 ID。在设计调试日志时,用 pthread_t 类型的 ID 来标识进程就不太合适了。
采用 pid_t 类型的线程 ID 来唯一标识进程由以下优势:
- 返回类型是 pid_t 类型,进程之间不会存在重复的线程 ID,而且不同线程之间也不会重复,在任意时刻都是全局唯一的值
- proc 中记录了线程的相关信息,可以方便的查看 /proc/pid/task/tid 来获取线程对应的信息
- ps 命令提供了查看线程信息的 -L 选项,可以通过输出中的 LWP 和 NLWP,来查看同一个线程组的线程个数和线程 ID 的信息
如:
#include <stdio.h> #include <pthread.h> #include <sys/syscall.h> void* thread_work(void* param) { int tid = syscall(SYS_gettid); printf("tid : %d ", tid); sleep(1000); return NULL; } int main() { pthread_t tid1 = 0; pthread_t tid2 = 0; pthread_t tid3 = 0; int ret = pthread_create(&tid1, NULL, thread_work, NULL); ret = pthread_create(&tid2, NULL, thread_work, NULL); ret = pthread_create(&tid3, NULL, thread_work, NULL); ret = pthread_join(tid1, NULL); ret = pthread_join(tid2, NULL); ret = pthread_join(tid3, NULL); return 0; }
ps 命令查看线程 ID:
jingyg@jingyg:~$ ps -efL | grep hello_world jingyg 4038 3436 4038 0 4 03:54 pts/1 00:00:00 ./hello_world jingyg 4038 3436 4039 0 4 03:54 pts/1 00:00:00 ./hello_world jingyg 4038 3436 4040 0 4 03:54 pts/1 00:00:00 ./hello_world jingyg 4038 3436 4041 0 4 03:54 pts/1 00:00:00 ./hello_world
查看 proc:
jingyg@jingyg:~$ ll /proc/4038/task/ total 0 dr-xr-xr-x 6 jingyg jingyg 0 Jul 20 03:54 ./ dr-xr-xr-x 9 jingyg jingyg 0 Jul 20 03:54 ../ dr-xr-xr-x 6 jingyg jingyg 0 Jul 20 03:54 4038/ dr-xr-xr-x 6 jingyg jingyg 0 Jul 20 03:54 4039/ dr-xr-xr-x 6 jingyg jingyg 0 Jul 20 03:54 4040/ dr-xr-xr-x 6 jingyg jingyg 0 Jul 20 03:54 4041/