1 线程ID获取方法
linux下获取线程有两种方法:
1)gettid或者类似gettid的方法
2)直接调用pthread_self()
gettid 获取的是内核中线程ID,而pthread_self 是posix描述的线程ID。
通过执行man手册,我们也能发现他们的区别:
SYNOPSIS
#include <sys/types.h>
pid_t gettid(void);
Note: There is no glibc wrapper for this system call; see NOTES.
DESCRIPTION
gettid() returns the caller's thread ID (TID). In a single-threaded process, the thread ID is equal to the process ID (PID, as returned by getpid(2)). In
a multithreaded process, all threads have the same PID, but each one has a unique TID. For further details, see the discussion of CLONE_THREAD in clone(2).
对于单线程的进程,内核中tid==pid,对于多线程进程,他们有相同的pid,不同的tid。tid用于描述内核真实的pid和tid信息。
DESCRIPTION
The pthread_self() function returns the ID of the calling thread. This is the same value that is returned in *thread in the pthread_create(3) call that
created this thread.
RETURN VALUE
This function always succeeds, returning the calling thread's ID.
he thread ID returned by pthread_self() is not the same thing as the kernel thread ID returned by a call to gettid(2).
pthread_self返回的是posix定义的线程ID,man手册明确说明了和内核线程tid不同。它只是用来区分某个进程中不同的线程,当一个线程退出后,新创建的线程可以复用原来的id。
2 为什么需要两个ID描述线程?
通过执行如下代码, 我们也能发现他们的区别:
-
#include <stdio.h>
-
#include <unistd.h>
-
#include <stdlib.h>
-
#include <pthread.h>
-
#include <sys/types.h>
-
#include <sys/wait.h>
-
//#include <sys/syscall.h>
-
-
#define __NR_gettid 186
-
void *f()
-
{
-
int status;
-
printf(
"begin: pid: %d, tid:%ld, self: %ld\n", getpid(), (
long
int)syscall(__NR_gettid), pthread_self());
-
int ret = fork();
-
if(ret ==
0){
-
printf(
"[child] pid: %d, tid:%ld, self: %ld\n", getpid(), (
long
int)syscall(__NR_gettid), pthread_self());
-
}
else
if(ret >
0){
-
printf(
"[parent] pid: %d, tid:%ld, self: %ld\n", getpid(), (
long
int)syscall(__NR_gettid), pthread_self());
-
waitpid(
-1, &status,
0);
-
}
-
}
-
-
int main()
-
{
-
-
int i =
0;
-
pthread_t pth[
1];
-
while(i++<
1){
-
pthread_create(&pth[i],
NULL, f,
NULL);
-
sleep(
1);
-
}
-
pause();
-
}
描述线程的id,为什么需要两个不同的ID呢?这是因为线程库实际上由两部分组成:内核的线程支持+用户态的库支持(glibc),Linux在早期内核不支持线程的时候glibc就在库中(用户态)以纤程(就是用户态线程)的方式支持多线程了,POSIX thread只要求了用户编程的调用接口对内核接口没有要求。
linux上的线程实现就是在内核支持的基础上以POSIX thread的方式对外封装了接口,所以才会有两个ID的问题。
3 内部实现
glibc中并没有直接提供gettid函数,与之类似的方法是执行系统调用。
在头文件 /usr/include/x86_64-linux-gnu/asm/unistd_64.h 中找到__NR_gettid 的定义:
#define __NR_gettid 186
gettid的包裹实现: syscall(__NR_gettid)
glibc中有如下调用:
tid在内核中就是一个普通进程。
#define CHECK_TPP_PRIORITY(normal, boosted) \ do \ { \ pid_t tid = syscall (__NR_gettid); \ \ struct sched_param cep_sp; \ int cep_policy; \ if (pthread_getschedparam (pthread_self (), &cep_policy, \ &cep_sp) != 0) \ { \ puts ("getschedparam failed"); \ ret = 1; \ } \ else if (cep_sp.sched_priority != (normal)) \ { \ printf ("unexpected priority %d != %d\n", \ cep_sp.sched_priority, (normal)); \ } \ if (syscall (__NR_sched_getparam, tid, &cep_sp) == 0 \ && cep_sp.sched_priority != (boosted)) \ { \ printf ("unexpected boosted priority %d != %d\n", \ cep_sp.sched_priority, (boosted)); \ ret = 1; \ } \ } \ while (0)
在glibc源码中,发现posix中pthread_self的实现如下:
-
pthread_t
-
__pthread_self (
void)
-
{
-
return (
pthread_t) THREAD_SELF;
-
}
-
strong_alias (__pthread_self, pthread_self)
# define THREAD_SELF \ ({ struct pthread *__self; \ asm ("mov %%fs:%c1,%0" : "=r" (__self) \ : "i" (offsetof (struct pthread, header.self))); \ __self;})
-
struct pthread
-
{
-
union
-
{
-
#if !TLS_DTV_AT_TP
-
/* This overlaps the TCB as used for TLS without threads (see tls.h). */
-
tcbhead_t header;
-
#else
-
struct
-
{
-
int multiple_threads;
-
int gscope_flag;
-
# ifndef __ASSUME_PRIVATE_FUTEX
-
int private_futex;
-
# endif
-
} header;
-
#endif
typedef struct { void *tcb; /* Pointer to the TCB. Not necessarily the thread descriptor used by libpthread. */ dtv_t *dtv; void *self; /* Pointer to the thread descriptor. */ int multiple_threads; int gscope_flag; uintptr_t sysinfo; uintptr_t stack_guard; uintptr_t pointer_guard; unsigned long int vgetcpu_cache[2]; # ifndef __ASSUME_PRIVATE_FUTEX int private_futex; # else int __glibc_reserved1; # endif int rtld_must_xmm_save; /* Reservation of some values for the TM ABI. */ void *__private_tm[4]; /* GCC split stack support. */ void *__private_ss; long int __glibc_reserved2; /* Have space for the post-AVX register size. */ __128bits rtld_savespace_sse[8][4] __attribute__ ((aligned (32))); void *__padding[8]; } tcbhead_t;
#define offsetof(Type, Member) ((size_t) &((Type *) NULL)->Member)
pthread_self 即是获取线程控制块tcb首地址 相对于进程数据的段的偏移, 注:pthread_create也是返回该值。
4 总结
gettid 获取的是内核中真实线程ID, 对于多线程进程来说,每个tid实际是不一样的。
而pthread_self获取的是相对于进程的线程控制块的首地址, 只是用来描述统一进程中的不同线程,
例子中,在线程中调用fork,只会将当前活动线程设置为活动(其他线程终止),且进程使用的都是虚拟地址,所以产生的pthread_self() 是相同的。
上述不匹配,对程序的实际运行,并没有影响,因为他们的tid是不同的。
本文中有关线程模型的基础知识,请参见: