不落辰

知不可乎骤得,托遗响于悲风

0%

c++11_thread_local

  • 起因:在看Muduo如何获取线程id时,发现有个__thread

CurrentThread : get the thread id of current Thread

  • 获取线程系统调用:gettid(),开销较大,为提高效率,尽量少执行gettid,因此,在第一次得到gettid之后就将tid存起来,下次要用的时候直接返回。
  • currentThread.h
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //  user通过调用CurrentThread::tid()来获取当前线程的tid
    namespace CurrentThread
    {
    extern thread_local int t_cachedTid; // 每个线程独立的全局变量
    void cacheTid();
    inline int tid()
    {
    if(__glibc_unlikely(t_cachedTid == 0))
    // if(__builtin_expect(t_cachedTid == 0, 0))
    // 告诉编译器 t_cacheTid == 0这件事的值 很有可能为0. 帮助编译器进行分支预测
    {
    cacheTid();
    }
    return t_cachedTid;
    }
    }
  • currentThread.cpp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    namespace CurrentThread
    {
    thread_local int t_cachedTid = 0;
    void cacheTid()
    {
    if(t_cachedTid == 0)
    {
    // 通过Linux系统调用gettid 获取当前线程的tid值
    // 系统调用没提供gettid这个接口 因此需要我们自己通过syscall 进行调用
    // 见Blog csapp-8-异常控制流中的syscall n
    // syscall + 系统调用编号 即可进行系统调用
    t_cachedTid = static_cast<pid_t>(::syscall(SYS_gettid));
    }
    }
    }

builtin_expected

  • __builtin_expect() 是 GCC (version >= 2.96)提供给程序员使用的,目的是将“分支转移”的信息提供给编译器,这样编译器可以对代码进行优化,以减少指令跳转带来的性能下降。
  • __builtin_expect((x),1)表示 x 的值为真的可能性更大;
  • __builtin_expect((x),0)表示 x 的值为假的可能性更大。
  • likely,unlikely
    1
    2
    # define __glibc_unlikely(cond)	__builtin_expect ((cond), 0)
    # define __glibc_likely(cond) __builtin_expect ((cond), 1)
  • 所以
    • t_cacheTid == 0的可能性很小
      1
      2
      3
      4
      5
      6
      if(__glibc_unlikely(t_cachedTid == 0))
      // if(__builtin_expect(t_cachedTid == 0, 0))
      // 告诉编译器 t_cacheTid == 0这件事的值 很有可能为0. 帮助编译器进行分支预测
      {
      cacheTid(); // 获取Tid
      }

tid

  • top -Hp pid​​​:查看某个进程的线程信息,​​-H​​​ 显示线程信息,​​-p​​指定pid
  • pthread_self返回的不是线程真正的独一无二的那个的tid
  • std::this_thread::get_id() 和 pthread_self相同
  • gettid返回的是线程真正的独一无二的的tid
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    int main()
    {
    printf("gettid = %d (syscall real thread id)\n",gettid());
    printf("pthread_self = %ld (POSIX)\n",pthread_self());
    std::cout << "c++11::get_id() = "<<std::this_thread::get_id()<<std::endl;;
    return 0;
    }

    shc@DESKTOP-TVUERHD:~/Code$ ./tid
    gettid = 31721 (syscall real thread id)
    pthread_self = 140053980942144 (POSIX)
    c++11::get_id() = 1

thread_local

作用

  • 存储类说明符以及链接

  • 参考

  • 代码示例

  • 参考

  • thread_local:存储类说明符

    • thread_local 关键词只能搭配在 [命名空间作用域声明的对象]、[在块作用域声明的对象] 及 [静态数据成员]。
    • 它指示对象具有线程存储期
      • 对象的存储在线程开始时分配,而在线程结束时解分配。
      • 每个线程拥有其自身的对象实例。
    • 如果对**块作用域变量(函数内)**只应用了 thread_local 这一个存储类说明符,那么同时也意味着应用了 static。(所以如果在一个函数内声明了thread_local 那么就相当于是这个是一个独属于这个线程的static局部变量)
    • 它能与 static 或 extern 结合,以分别指定内部或外部链接(但静态数据成员始终拥有外部链接)。
    • 一言以蔽之:属于线程的变量。线程间独立,每个线程在自己的栈上有自己的副本。
    • 应当在声明时初始化,该初始化语句在本 thread 中只会执行一次
  • 比__thread

    • __thread:是GCC的关键字,非Unix编程或C语言标准,属于编译器自己实现。__thread只能修饰基础数据类型或者POD类型。
      • 所谓POD就是C语言中传统的struct类型。即无拷贝、析构函数的结构体。
    • thread_local:可以修饰函数内局部对象(自动static);除标准类型外,还可以修饰C++的对象如vector;还可以修饰类中的成员变量,但只能是static。
      • thread_local修饰类内成员变量:static thread_local的变量也需要在类外进行初始化,并且带着thread_local关键字 thread_local int A::count = 0;

code

  • 如下,一个thread_local的全局变量

    • 属于线程的全局变量。全局可见,但每个线程有自己的副本。
      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
      #include <iostream>
      #include <thread>
      #include <mutex>

      std::mutex cout_mutex; // for multiple print

      thread_local int x = 0;

      void thread_func(const std::string& thread_name) {
      for (int i = 0; i < 3; ++i) {
      std::lock_guard<std::mutex> lock(cout_mutex);
      std::cout << "thread[" << thread_name << "]: x = " << ++x << std::endl;
      }
      return;
      }

      int main() {
      std::thread t1(thread_func, "t1");
      std::thread t2(thread_func, "t2");
      t1.join();
      t2.join();
      return 0;
      }

      thread[t1]: x = 1
      thread[t1]: x = 2
      thread[t1]: x = 3
      thread[t2]: x = 1
      thread[t2]: x = 2
      thread[t2]: x = 3
  • 一个thread_local的局部变量

    • 只用thread_local的话相当于static thread_local
      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
      #include <thread>
      #include <mutex>
      std::mutex cout_mutex; //方便多线程打印

      void thread_func(const std::string& thread_name) {
      for (int i = 0; i < 3; ++i) {
      thread_local int x = 0;
      std::lock_guard<std::mutex> lock(cout_mutex);
      std::cout << "thread[" << thread_name << "]: x = " << ++x << std::endl;
      }
      return;
      }

      int main() {
      std::thread t1(thread_func, "t1");
      std::thread t2(thread_func, "t2");
      t1.join();
      t2.join();
      return 0;
      }

      thread[t1]: x = 1
      thread[t1]: x = 2
      thread[t1]: x = 3
      thread[t2]: x = 1
      thread[t2]: x = 2
      thread[t2]: x = 3
  • thread_local 修饰类成员变量

    • 必须是static
  • thread_local 修饰的对象只在其作用域内可见

extern

  • header.h : extern xx
    • 存在一个这样的变量xx。他在某一个.c文件中被定义。任何一个include该header.h 都可以看到并使用这个变量xx。
    • 被多个文件include,但只被某个.c定义了一次。
    • 在.c文件编译完成,进入链接阶段,linker会将所有xx的引用 和 一个 xx的定义关联起来。(xx的定义到已经compiled source files中去找)