実装依存なので注意。今回の話は Linux (glibc) についてです。
pthread_create 第一引数や、pthread_self戻り値で使用される pthread_t 型であるがその値の意味について考えたことは無かった。
pthread_t 型をマニュアルなどを確認すると「スレッドの識別子」と記載されているが、システムコール(gettid)の返す値とは異なり、無意味な値だと考えていた。ところが、前回の調査(Linux の pthread のデフォルトスタックサイズについて « 余談ですが……)を行なったときに、あるところのポインタ値であることが分かった。
まず、pthread_t の値(ポインタ値)を確認しみる。値自体は gdb で “info threads” としたときにも出力されます。
(gdb) info threads
2 Thread 0xb7fedb90 (LWP 8398) 0xb7fff424 in __kernel_vsyscall ()
* 1 Thread 0xb7fee6c0 (LWP 8395) 0xb7fff424 in __kernel_vsyscall ()
(gdb)
スレッドの1がメインスレッド(main関数)で、スレッドの2がpthread_createで作成したスレッドということになる。どちらも値がかなり大きく、あまり意味のあるように思えない。
pthread_createの実装を確認してみた。
nptl/pthread_create.c
int __pthread_create_2_1 (newthread, attr, start_routine, arg) pthread_t *newthread; const pthread_attr_t *attr; void *(*start_routine) (void *); void *arg; { STACK_VARIABLES; const struct pthread_attr *iattr = (struct pthread_attr *) attr; if (iattr == NULL) /* Is this the best idea? On NUMA machines this could mean accessing far-away memory. */ iattr = &default_attr; struct pthread *pd = NULL; int err = ALLOCATE_STACK (iattr, &pd); //(中略) /* Pass the descriptor to the caller. */ *newthread = (pthread_t) pd; /* Start the thread. */ return create_thread (pd, iattr, STACK_VARIABLES_ARGS); }
ALLOCATE_STACK というマクロの第二引数のポインタが設定されるようだ。ALLOCATE_STACK マクロは allocate_stack に展開される。その関数の中で以下のとおり実施している。
static int allocate_stack (const struct pthread_attr *attr, struct pthread **pdp, ALLOCATE_STACK_PARMS) { struct pthread *pd; size_t size; size_t pagesize_m1 = __getpagesize () - 1; void *stacktop; assert (attr != NULL); assert (powerof2 (pagesize_m1 + 1)); assert (TCB_ALIGNMENT >= STACK_ALIGN); /* Get the stack size from the attribute if it is set. Otherwise we use the default we determined at start time. */ size = attr->stacksize ?: __default_stacksize; /* Get memory for the stack. */ if (__builtin_expect (attr->flags & ATTR_FLAG_STACKADDR, 0)) { //スタックアドレスが設定済の場合(中略) } else { /* Allocate some anonymous memory. If possible use the cache. */ size_t guardsize; size_t reqsize; void *mem; const int prot = (PROT_READ | PROT_WRITE | ((GL(dl_stack_flags) & PF_X) ? PROT_EXEC : 0)); // (中略) mem = mmap (NULL, size, prot, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); /* Place the thread descriptor at the end of the stack. */ pd = (struct pthread *) ((char *) mem + size - coloring) - 1; // (中略) } // (中略) /* We place the thread descriptor at the end of the stack. */ *pdp = pd; // (中略) return 0; }
(スタックアドレスを明示的に設定した場合の処理は中略とした。)
注目すべきは、mmapした領域(スタック領域)のmmap領域サイズの最後尾辺りに、pd というポインタを設定して、それを引数の *pdp に設定している、という点である。
つまり、pthread_t の値は、自身のスレッドのスタックの底(近辺)のアドレスを意味している。ソース内のコメントもそれを意味した内容が記載してある。
テストコメント