thread_local
是 C++11 引入的一个关键字,它的主要作用是用于声明线程私有变量,该变量在线程内部的多个函数之间可以共享,在线程和线程之间则是独立的、不共享的。
1. 问题场景
局部变量可以在线程之间独立,在线程内部则生命周期只在某个函数内部。如果想多个线程关联函数访问的话,就必须使用全局变量,但是又会带来资源竞争问。另外,局部变量无法保持函数执行的状态,因为函数执行结束之后就会被销毁。如果使用静态局部变量,在多个线程中中该变量也会带来资源竞争问题。
也就是说,我们之前定义的变量在多线程场景下,会导致变量被多个线程共享,线程无法独享。所以,C++11 专门引入一个关键字thread_local 来给线程定义具有线程生命周期的变量。
在单线程程序中,可以根据使用场景选择合适的变量。例如:
- 需要在函数内部独立使用的数据,可以使用局部变量
- 需要跨函数使用的数据,可以使用全局变量
- 需要在函数执行过程中持续保存的数据,可以使用静态局部变量
然而,当进入多线程场景时,这些针对单线程的变量策略可能会显得不够适用,具体表现如下:
- 需要跨线程函数使用的数据,全局变量会出现资源竞争问题
- 如果线程运行期间需要保存一些信息,静态局部变量又是线程共享的,也可能会导致资源竞争问题
即:哪些具有整个程序生命周期的变量在多线程的场景下,会出现资源竞争问题。所以,我们就想能不能把这些变量的生命周期给压缩到线程的生周期内,这样线程内部可以安全使用,线程之间也不会冲突。这就是 thread_local 出现的目的。
#include<iostream> #include<thread> #include<mutex> std::mutex mtx; // 全局变量可以实现跨函数使用,但是线程并不独立 int num = 10; void step1() { num += 1; } void step2() { num += 2; } void do_work() { step1(); step2(); std::lock_guard<std::mutex> lock(mtx); std::cout << "thread:" << std::this_thread::get_id() << " num:" << num << std::endl; } void test() { std::thread t1(do_work); std::thread t2(do_work); t1.join(); t2.join(); }
局部变量的局限性
希望在线程运行期间能够持续存储一些信息。由于普通局部变量的生命周期仅限于函数调用期间,函数结束后会被销毁。而静态局部变量又是线程共享。所以在当前场景下就不太合适。
#include <iostream> #include <thread> #include<mutex> std::mutex mtx; void counter() { // 局部变量函数作用域,函数结束会被回收 // int val = 10; // 静态变量多线程共享,变量不能线程独立 static int val = 10; val += 1; std::lock_guard<std::mutex> lock(mtx); std::cout << "thread " << std::this_thread::get_id() << ": val = " << val << std::endl; } void do_work() { for (int i = 0; i < 5; ++i) { std::this_thread::sleep_for(std::chrono::milliseconds(500)); counter(); } } void test() { std::thread t1(do_work); std::thread t2(do_work); t1.join(); t2.join(); }
简言之:在某些场景下,我们需要给线程单独去创建数据,线程内共享,线程之间独立,并且线程结束自动回收。这就是线程局部存储 TLS 要解决的问题。
2. 用法
使用 thread_local
可以声明一个变量,使其在每个线程中都有独立的副本。线程结束时,这些局部变量会被销毁。thread_local
变量的作用域和普通变量相同,只是在不同的线程中,每个线程都会有独立的存储空间。例如,线程局部变量可以是函数内的局部变量、类的成员变量,甚至是全局变量。