Contents

Thread

Thread

Thread Model

  1. LinuxThreads
    • Default thread implementation since Linux kernel 2.0
    • Obsolete
  2. Next Generation POSIX Thread (NGPT)
    • IBM developed version of POSIX thread library
    • Abandoned
  3. Native POSIX Thread Library(NPTL)
    • Developed by RedHat
    • Better performance and scalability than LinuxThreads
    • Since Linux Kernel 2.6, glibc 2.3.5

Memory Layout

  • Heap:被所有线程共享。
  • Stack:每个线程都有自己独立的 stack。

Thread Stack

/images/2021/05/20210526_230935Snipaste_2021-05-26_23-09-07.png

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
low  ┌──────────┐
     │...       │ ─┐                  ─┐
     ├──────────┤  │                   │
     │list      │  │                   │
     ├──────────┤  ├─►pthread struct   │
     │tid       │  │                   │
     ├──────────┤  │                   │
     │...       │ ─┘                   │
     ├──────────┤                      ├─►thread 1
     │          │                      │
     │stack     │                      │
     │          │                     ─┘
     ├──────────┤
     │          │ ───►stack guard(4k)
     ├──────────┤
     │...       │ ─┐
     ├──────────┤  │
     │list      │  │
     ├──────────┤  │
     │tid       │  │
     ├──────────┤  │
     │...       │  │
     ├──────────┤  ├─►thread 2
     │          │  │
     │stack     │  │
     │          │ ─┘
high └──────────┘

Allocate

  1. Implementation

    线程栈分配逻辑如下(基于 glibc 2.29.9000),

    1. __pthread_create_2_1 创建 thread 时,调用 ALLOCATE_STACK 宏创建 Thread stack。

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      
         int
         __pthread_create_2_1 (pthread_t *newthread, const pthread_attr_t *attr,
                               void *(*start_routine) (void *), void *arg)
         {
           // ...
           struct pthread *pd = NULL;
           int err = ALLOCATE_STACK (iattr, &pd);
           int retval = 0;
           // ...
         }
      
    2. ALLOCATE_STACK 宏调用 allcate_stack 来执行具体的创建逻辑。

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      
         static int
         allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
                         ALLOCATE_STACK_PARMS)
         {
           // ...
           // 从 cache 中查找空闲的栈内存
           pd = get_cached_stack (&size, &mem);
           // 没有空闲的则 mmap 创建
           if (pd == NULL)
             {
               // ...
               mem = __mmap (NULL, size, (guardsize == 0) ? prot : PROT_NONE,
                             MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
               // ...
      
               /* Allocate the DTV for this thread.  */
               _dl_allocate_tls (TLS_TPADJ (pd));
               // ...
      
               /* Prepare to modify global data.  */
               lll_lock (stack_cache_lock, LLL_PRIVATE);
               /* And add to the list of stacks in use.  */
               // 将 stack 内存挂到 stack_used list 中
               stack_list_add (&pd->list, &stack_used);
               lll_unlock (stack_cache_lock, LLL_PRIVATE);
      
               // ...
             }
           // ...
           // 更新全局栈内存指针
           *stack = pd->stackblock;
           *stacksize = stacktop - *stack;
           // ...
         }
      

Thread-Local Storage

Thread-local storage(TLS)提供了一种机制,使得每个 thread 都拥有一份变量的实例,对 TLS 中变量进行的修改仅对当前 thread 可见。

Common Implementation

  1. pthread:thread-specific data (TSD)
  2. ELF(Executable and Linkable Format) TLS
    • gcc: __thread
    • c11: _Thread_local
    • c++11: thread_local

Example Usages

  1. adext ThreadData 主要用于存储 iconf ctx,确保每个 thread 使用自己的 ctx 来 merge cgi 下发的实验配置。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
       static __thread ThreadData* s_td = NULL;
    
       class ThreadData {
           public:
               ThreadData();
               ~ThreadData();
    
               int init();
    
               void reset();
    
               int set_request_info(int bucketid,bs::RankFrontendResp *admergeResult);
    
               ::iconf::Context* get_context() {
                   return _ctx;
               }
           private:
               ::iconf::Context* _ctx;
       };
    
  2. jemalloc 使用 TLS 来保存每个线程特有的数据,避免锁竞争。

    1
    2
    3
    4
    5
    
       #define	malloc_tsd_data(a_attr, a_name, a_type, a_initializer)		\
       a_attr __thread a_type JEMALLOC_TLS_MODEL				\
           a_name##tsd_tls = a_initializer;					\
       a_attr pthread_key_t	a_name##tsd_tsd;				\
       a_attr bool		a_name##tsd_booted = false;
    
  3. POSIX errno

    errno is thread-local; setting it in one thread does not affect its value in any other thread.

Implementation

pthread:thread-specific data (TSD)

Example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <cassert>
#include <iostream>
#include <pthread.h>

pthread_key_t tsd_key;

void free_memory(void *arg) {
    int *p = reinterpret_cast<int *>(arg);
    delete p;
}

void *run(void *arg) {
    int *d = reinterpret_cast<int *>(arg);
    void *p = pthread_getspecific(tsd_key);
    assert(p == nullptr);
    p = new int(*d);

    int *pa = reinterpret_cast<int *>(p);
    *pa += 5;

    int err = pthread_setspecific(tsd_key, p);
    assert(err == 0);

    pthread_t tid = pthread_self();
    p = pthread_getspecific(tsd_key);
    int *a = reinterpret_cast<int *>(p);

    std::cout << *a << " in thread " << tid << std::endl;

    return nullptr;
}

int main(int argc, char *argv[]) {
    int err = pthread_key_create(&tsd_key, free_memory);
    assert(err == 0);

    pthread_t tid[2];

    int data[] = {10, 20};

    pthread_create(&tid[0], nullptr, run, &data[0]);
    pthread_create(&tid[1], nullptr, run, &data[1]);

    pthread_join(tid[0], nullptr);
    pthread_join(tid[1], nullptr);

    err = pthread_key_delete(tsd_key);
    assert(err == 0);
    return 0;
}

// output
// 25 in thread 139672264689408
// 15 in thread 139672273082112

Memory Layout

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 low ┌─────────────────┐
     │header           │ ───►tcbhead_t struct ─┐
     ├─────────────────┤                       │
     │list             │                       │                     ─┐
     ├─────────────────┤                       │                      │
     │tid              │                       │                      │
     ├─────────────────┤                       ├─►pthread struct      │
     │specific_1stblock│                       │                      │
     ├─────────────────┤                       │                      │
     │specific         │                       │                      │
     ├─────────────────┤                       │                      ├─►thread 1
     │...              │                      ─┘                      │
     ├─────────────────┤                                              │
     │                 │                                              │
     │stack            │                                              │
     │                 │                                             ─┘
     ├─────────────────┤
     │                 │ ───►stack guard(4k)
     ├─────────────────┤
     │header           │ ─┐
     ├─────────────────┤  │
     │list             │  │
     ├─────────────────┤  │
     │tid              │  │
     ├─────────────────┤  │
     │...              │  │
     ├─────────────────┤  ├─►thread 2
     │                 │  │
     │stack            │  │
     │                 │ ─┘
high └─────────────────┘

Implementation

  1. 全局分配唯一 key
  2. 各个 thread 存储自己的 key-value map

以下分析基于 glibc 2.29.9000。

  1. 如何访问 Thread Stack

    TSD 是存储在各个线程的内存中,要访问 TSD,就需要一种方法来访问线程栈。

    glibc 通过调用 clone 实现创建线程。如果提供了 CLONE_SETTLS ,那么会将 tls 存入 fs 寄存器。

    1
    2
    
    int clone(int (*fn)(void *), void *stack, int flags, void *arg, ...
             /* pid_t *parent_tid, void *tls, pid_t *child_tid */ );
    

    在调用 clone 时, tls 是 struct pthread 的地址。从而可以通过 fs 寄存器来访问到线程栈。

    1
    2
    3
    4
    
    TLS_DEFINE_INIT_TP (tp, pd);
    if (__glibc_unlikely (ARCH_CLONE (&start_thread, STACK_VARIABLES_ARGS,
                                      clone_flags, pd, &pd->tid, tp, &pd->tid)
                          == -1))
    

    glibc,以及各种 TLS 实现中对线程栈的访问都是利用 fs 实现的。

    fs 寄存器可以通过如下方式获得:

    1. arch_prctl

      1
      
         arch_prctl(ARCH_GET_FS, &pthread_addr);
      
    2. asm %fs

      1
      2
      3
      4
      5
      6
      7
      8
      
         # define THREAD_SELF \
           ({ struct pthread *__self;                                                      \
              asm ("mov %%fs:%c1,%0" : "=r" (__self)                                      \
                   : "i" (offsetof (struct pthread, header.self)));                       \
              __self;})
      
         struct pthread *self;
         self = THREAD_SELF;
      
  2. Create

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    #define PTHREAD_KEYS_MAX        1024
    
    typedef unsigned int pthread_key_t;
    
    /* Thread-local data handling.  */
    struct pthread_key_struct
    {
      /* Sequence numbers.  Even numbers indicated vacant entries.  Note
         that zero is even.  We use uintptr_t to not require padding on
         32- and 64-bit machines.  On 64-bit machines it helps to avoid
         wrapping, too.  */
      uintptr_t seq;
      /* Destructor for the data.  */
      void (*destr) (void *);
    };
    
    struct pthread_key_struct __pthread_keys[PTHREAD_KEYS_MAX]
    

    在主线程中调用 pthread_key_create 完成 key 的创建。glibc 定义了全局数组 __pthread_keys 来管理 key,每个进程最多创建 1024 个 key。

    数组中每个元素的:

    1. seq 用于判断 key 是否被使用,为奇数则被使用
    2. destr 用于存储释放资源的函数指针
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    #define KEY_UNUSED(p) (((p) & 1) == 0)
    
    int
    __pthread_key_create (pthread_key_t *key, void (*destr) (void *))
    {
      /* Find a slot in __pthread_keys which is unused.  */
      for (size_t cnt = 0; cnt < PTHREAD_KEYS_MAX; ++cnt)
        {
        // ...
              /* Remember the destructor.  */
              __pthread_keys[cnt].destr = destr;
              /* Return the key to the caller.  */
              *key = cnt;
              /* The call succeeded.  */
              return 0;
        }
      // ...
    }
    
  3. Set

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    #define PTHREAD_KEY_2NDLEVEL_SIZE       32
    
    // 32
    #define PTHREAD_KEY_1STLEVEL_SIZE \
      ((PTHREAD_KEYS_MAX + PTHREAD_KEY_2NDLEVEL_SIZE - 1) \
       / PTHREAD_KEY_2NDLEVEL_SIZE)
    
    struct pthread_key_data
    {
      /* Sequence number.  We use uintptr_t to not require padding on
         32- and 64-bit machines.  On 64-bit machines it helps to avoid
         wrapping, too.  */
      uintptr_t seq;
      /* Data pointer.  */
      void *data;
    } specific_1stblock[PTHREAD_KEY_2NDLEVEL_SIZE];
    /* Two-level array for the thread-specific data.  */
    struct pthread_key_data *specific[PTHREAD_KEY_1STLEVEL_SIZE];
    

    各个线程将 key-value 存储到线程栈中,

    1. 如果 key < 32,到存储到 specific_1stblock
    2. 如果 32 <= key < 1024,存储到二维数组 specific
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    
    int
    __pthread_setspecific (pthread_key_t key, const void *value)
    {
      // ...
      struct pthread *self;
      self = THREAD_SELF;
      /* Special case access to the first 2nd-level block.  This is the
         usual case.  */
      if (__glibc_likely (key < PTHREAD_KEY_2NDLEVEL_SIZE))
        {
          // ...
          level2 = &self->specific_1stblock[key];
          // ...
        }
      else
        {
          // ...
          idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZE;
          idx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE;
          // ...
          /* This is the second level array.  Allocate it if necessary.  */
          level2 = THREAD_GETMEM_NC (self, specific, idx1st);
          if (level2 == NULL)
            {
              // 分配 specific 二维数组
              // ...
            }
          /* Pointer to the right array element.  */
          level2 = &level2[idx2nd];
        }
      /* Store the data and the sequence number so that we can recognize
         stale data.  */
      level2->seq = seq;
      level2->data = (void *) value;
      return 0;
    }
    
  4. Get

    根据 key 的取值范围,决定访问 specific_1stblock ,还是 specific 。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    void *
    __pthread_getspecific (pthread_key_t key)
    {
      // ...
      if (__glibc_likely (key < PTHREAD_KEY_2NDLEVEL_SIZE))
        data = &THREAD_SELF->specific_1stblock[key];
      else
        {
          // ...
          unsigned int idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZE;
          unsigned int idx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE;
          // ...
          data = &level2[idx2nd];
        }
    
      void *result = data->data;
      // ...
      return result;
    }
    
  5. Delete

    pthread_key_delete 会把 seq++,置为偶数,表示 key 为被使用,并不会真正的调用 destr 来进行资源的释放。

    当线程执行完毕时, __nptl_deallocate_tsd 调用每个 key 的 destr 执行清理。

ELF TLS

pthread TSD 全局分配 key,需要调用 glibc API 在运行时完成 TSD 的构造和使用,比较复杂,影响效率。因此就有了 ELF TLS。

ELF TLS 是编译器(gcc/clang)的扩展特性,其实现需要如下方面的支持:

  1. ELF 文件需要区分普通静态全局变量和 TLS 变量
  2. 程序启动时,动态链接器需要初始化 TLS 变量的段
  3. 线程运行时,需要为 TLS 分配专门的内存区域,以及动态计算 TLS 的地址

ELF TLS Section

普通 static 全局变量,根据是否初始化分别存储在 .data (已初始化)和 .bss (未初始化,初始化为 0),类似的 TLS 变量存储在 .tdata 和 .tbss 段。

由于 TLS 是各个线程私有的数据,因此线程是不能直接访问 .tdata 和 .tbss 段的。

运行时分配 TLS

  1. Memory Layout

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    
     low ┌──────┬──────────┐
         │      │...       │ ─┐                     ─┐
         │      ├──────────┤  │                      │
         │header│dtv       │  │                      │
         │      ├──────────┤  │                      │
         │      │...       │  │                      │
         ├──────┴──────────┤  ├─►pthread struct      │
         │list             │  │                      │
         ├─────────────────┤  │                      │
         │tid              │  │                      │
         ├─────────────────┤  │                      ├─►thread 1
         │...              │  │                      │
         ├─────────────────┤  │                      │
         │...              │ ─┘                      │
         ├─────────────────┤                         │
         │                 │                         │
         │stack            │                         │
         │                 │                        ─┘
         ├─────────────────┤
         │                 │ ───►stack guard(4k)
         ├─────────────────┤
         │header           │ ─┐
         ├─────────────────┤  │
         │list             │  │
         ├─────────────────┤  │
         │tid              │  │
         ├─────────────────┤  │
         │...              │  │
         ├─────────────────┤  ├─►thread 2
         │                 │  │
         │stack            │  │
         │                 │ ─┘
    high └─────────────────┘
    

    TLS 的内存布局用两种,对于 x86-64 是如下布局,

    /images/2021/05/Snipaste_2021-05-27_23-24-12.png

    tp(thread pointer,也是前面提到的 fs 寄存器的值) 指向 TCB,也就是 pthread struct,TCB 包含了一个 dtv(dynamic thread vector)指针,指向线程的 dtv 数组。

    dtv 存储了 TLS block 地址,通过 module ID 和 TLS 变量的偏移量来实现 TLS 变量的寻址。对于 exec,module ID 是 1;动态库( dlopen )的 module ID 直到加载的时候才会分配。

    根据 TLS 变量在何处被定义,其存储分为,

    1. 来自 exec、以及 shared object: main 函数之前加载的 TLS 变量存储在 TCB 前面的 TLS block。dtv[1] 指向这个 TLS block。
    2. dlopen 加载的 shared object:TLS 变量在 TLS Blocks for Dynamically-Loaded Modules。
  2. Allocate

    程序启动时,动态链接器将 exec、shared object 映射到进程地址空间,并进行相关初始化,

    1. 可能会对这两个段进行重定位,并把重定位后的段保存为 TLS 初始化镜像,之后便不再改动
    2. 调用 _dl_determine_tlsoffset 获得每个 module 中 TLS 变量的 offset(在编译的时候决定),offset 表示变量在 TLS block 中的偏移量
    3. 调用 _dl_allocate_tls_storage 分配 TLS 和 dtv 的内存
    4. 调用 _dl_allocate_tls_init 完成 TLS 和 dtv 的初始化
  3. Implementation

    1. 前面提到线程启动时, ALLOCATE_STACK 宏调用 allcate_stack 来执行创建线程栈的逻辑,其中调用 _dl_allocate_tls 是进行 TLS 和 dtv 的分配以及初始化。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    static int
    allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
                    ALLOCATE_STACK_PARMS)
    {
      // ...
      /* Allocate the DTV for this thread.  */
      _dl_allocate_tls (TLS_TPADJ (pd));
      // ...
    }
    
    1. _dl_allocate_tls_init 将每个 module 的 TLS 初始化镜像复制到 TLS block。
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    void *
    _dl_allocate_tls_init (void *result)
    {
      // ...
      while (1)
        {
              // ...
              dtv[map->l_tls_modid].pointer.val = TLS_DTV_UNALLOCATED;
              dtv[map->l_tls_modid].pointer.to_free = NULL;
              // ...
              dest = (char *) result - map->l_tls_offset;
              // ...
    
              /* Set up the DTV entry.  The simplified __tls_get_addr that
                 some platforms use in static programs requires it.  */
              dtv[map->l_tls_modid].pointer.val = dest;
              /* Copy the initialization image and clear the BSS part.  */
              memset (__mempcpy (dest, map->l_tls_initimage,
                                 map->l_tls_initimage_size), '\0',
                      map->l_tls_blocksize - map->l_tls_initimage_size);
        }
      // ...
    }
    

Example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <pthread.h>
#include <asm/prctl.h>

static int __thread var_1 = 0;
static int __thread var_2 = 1;

void* func(void* arg) {
    long pthread_addr = 0;
    arch_prctl(ARCH_GET_FS, &pthread_addr);

    var_1 = 1;
    var_2 = 2;

    printf(
        "&pthread = %p, &var_1 = %p, off_var_1 = %d,  &var_2 = %p, off_var_2 = "
        "%d\n",
        pthread_addr, &var_1, (long)&var_1 - pthread_addr, &var_2,
        (long)&var_2 - pthread_addr);
    return NULL;
}

int main() {
    pthread_t tids[3];
    int i = 0;

    for (i = 0; i < 3; ++i) {
        pthread_create(&tids[i], NULL, func, NULL);
    }

    for (i = 0; i < 3; ++i) {
        pthread_join(tids[i], NULL);
    }

    getchar();
    return 0;
}

// &pthread = 0x7ff58904d700, &var_1 = 0x7ff58904d6f8, off_var_1 = -8,  &var_2 = 0x7ff58904d6fc, off_var_2 = -4
// &pthread = 0x7ff58804b700, &var_1 = 0x7ff58804b6f8, off_var_1 = -8,  &var_2 = 0x7ff58804b6fc, off_var_2 = -4
// &pthread = 0x7ff58884c700, &var_1 = 0x7ff58884c6f8, off_var_1 = -8,  &var_2 = 0x7ff58884c6fc, off_var_2 = -4

TLS Access Models

根据 TLS 变量定义的位置,以及访问的位置不同,有以下 4 中使用 TLS 变量的模型。具体采用哪种模式由编译器和链接器共同决定。

  1. General Dynamic

    最通用的访问模型,任何地方定义的 TLS 变量,在任何地方访问,都可以使用这个方式。其他模型都是加上各种限制条件以后的优化。

    可以用这个公式来表示 dtv[ti_module].pointer + ti_offset 。实际可以使用 __tls_get_addr 来获取 TLS 变量地址。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    void *
    __tls_get_addr (GET_ADDR_ARGS)
    {
      dtv_t *dtv = THREAD_DTV ();
    
      if (__glibc_unlikely (dtv[0].counter != GL(dl_tls_generation)))
        return update_get_addr (GET_ADDR_PARAM);
    
      void *p = dtv[GET_ADDR_MODULE].pointer.val;
    
      if (__glibc_unlikely (p == TLS_DTV_UNALLOCATED))
        return tls_get_addr_tail (GET_ADDR_PARAM, dtv, NULL);
    
      return (char *) p + GET_ADDR_OFFSET;
    }
    
  2. Local Dynamic

    Local Dynamic 是对 General Dynamic 的优化。如果 TLS 变量在同一个模块被定义和使用,则可以使用这个模型。

    TLS 变量的 offset 在编译时可以确定,因此可以减少 __tls_get_addr 的调用。

  3. Initial Exec

    如果可以 TLS 变量在程序启动时就已分配好,则采用此模型。

  4. Local Exec

    对 Local Dynamic 的优化,如果 TLS 变量在 exec 中定义,在 exec 中访问,则使用这个模型。

    通过 fs 寄存器 + 偏移量访问。这种是最简单,也是最高效的方式,和访问局部变量没有区别。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
      var_1 = 1;
    401194:       64 c7 04 25 fc ff ff    movl   $0x1,%fs:0xfffffffffffffffc
    40119b:       ff 01 00 00 00
    4011a0:       64 48 8b 0c 25 00 00    mov    %fs:0x0,%rcx
    4011a7:       00 00
    4011a9:       48 8d 91 fc ff ff ff    lea    -0x4(%rcx),%rdx
      var_2 = 2;
    4011b0:       64 c7 04 25 f8 ff ff    movl   $0x2,%fs:0xfffffffffffffff8
    4011b7:       ff 02 00 00 00
    4011bc:       48 8d 89 f8 ff ff ff    lea    -0x8(%rcx),%rcx
    

gcc: __thread

pass

c11: _Thread_local

同 gcc __thread

c++11: thread_local

相对于 __thread , thread_local 可以在首次使用时自动初始化,线程结束时自动析构。

Summary

  1. pthread:thread-specific data (TSD)
    • 全局分配 key
    • 线程栈中的 pthread struct 存储自己线程的 key-value
    • 每个进程的 TSD 个数有限, PTHREAD_KEYS_MAX 个
    • 需要调用 glibc API 完成 TSD 的构造和使用,逻辑复杂
  2. ELF TLS
    • 效率比 TSD 高
    • 使用方便

References

Thread

  1. Understanding the Memory Layout of Linux Executables
  2. Understanding Linux /proc/id/maps
  3. Glibc 线程资源分配与释放–—线程栈
  4. 虚拟内存[02] Linux 中的各种栈:进程栈 线程栈 内核栈 中断栈
  5. Threads
  6. source code of userspace/glibc/nptl/

TLS

  1. gcc Thread-Local Storage
  2. ELF Handling For Thread-Local Storage
  3. A Deep dive into (implicit) Thread Local Storage
  4. All about thread-local storage
  5. Thread-local storage
  6. stackoverflow How does the gcc `_thread` work?
  7. 线程私有存储