- 起因:在看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
15namespace 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 - 所以
- t_cacheTid == 0的可能性很小
1
2
3
4
5
6if(__glibc_unlikely(t_cachedTid == 0))
// if(__builtin_expect(t_cachedTid == 0, 0))
// 告诉编译器 t_cacheTid == 0这件事的值 很有可能为0. 帮助编译器进行分支预测
{
cacheTid(); // 获取Tid
}
- t_cacheTid == 0的可能性很小
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
12int 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;
- thread_local修饰类内成员变量:static thread_local的变量也需要在类外进行初始化,并且带着thread_local关键字
- __thread:是GCC的关键字,非Unix编程或C语言标准,属于编译器自己实现。__thread只能修饰基础数据类型或者POD类型。
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
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
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
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中去找)