对于多线程程序,所有线程共享全局和静态变量,任何线程使用变量之后都会在其他线程可见,因此对于执行顺序非常重要的场景,需要使用多重方式来进行同步确保线程安全。但是,如果希望每个线程单独拥有一个全局或静态变量,所有线程都可以使用它,但是在每个线程中是单独存储的,那么就需要使用线程本地存储。
pthread库的实现
经典的pthread线程库提供了对线程本地存储的完全支持,具体需要使用如下三个函数:
//将需要共享的变量转换为void*指针,绑定到pthread_key_t关联的全局对象中
int pthread_setspecific(pthread_key_t key, const void *value);
//从绑定到pthread_key_t类型的全局变量中获取需要的数据,返回类型需要从void*转换为实际类型
void *pthread_getspecific(pthread_key_t key);
//创建一个全局关联变量,第二个参数是指定对用户关联的数据进行释放的handler
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
具体使用步骤:
- 创建一个类型为
pthread_key_t
类型的变量。 - 调用
pthread_key_create()
来创建该变量。该函数有两个参数,第一个参数就是上面声明的pthread_key_t
变量,第二个参数是一个清理函数,用来在线程释放该线程存储的时候被调用。该函数指针可以设成 NULL ,这样系统将调用默认的清理函数。 - 当线程中需要存储特殊值的时候,调用
pthread_setspcific()
。该函数有两个参数,第一个为前面声明的pthread_key_t
变量,第二个为void*
变量,这样你可以存储任何类型的值。 - 如果需要取出所存储的值,调用
pthread_getspecific()
。该函数的参数为前面提到的pthread_key_t
变量,该函数返回void *
类型的值。
C++封装
针对这些C风格函数操作,下面使用C++模板封装了一个范型版本。
template<typename T>
class ThreadSpecificUtil {
public:
static void init() {
pthread_once(&_s_once, initKey);
}
static T * get() {
return renterpret_cast<T *>(
pthread_getspecific(_s_key));
}
static int set(T *data) {
int ret = pthread_setspecific(
_s_key,
reinterpret_cast<void *>(data));
return ret;
}
private:
static void initKey() {
pthread_key_create(&_s_key, dataDestructor);
}
static void dataDestructor(void *p) {
if (NULL != p) {
T *data = reinterpret_cast<T *>(p);
delete data;
}
}
private:
static pthread_key_t _s_key;
static pthread_once_t _s_once;
};
template<typename T>
pthread_key_t ThreadSpecificUtil<T>::_s_key;
template<typename T>
pthread_once_t ThreadSpecificUtil<T>::_s_once = PTHREAD_ONCE_INIT;
上述使用的pthread_once
函数用来完成初始化工作,创建一个全局的pthread_key_t
类型的变量,保证多个线程中只有一个来完成这个初始化工作。
C++11
C++11标准新添加了thread_local
关键字,用来标识新的存储类型。
The storage class specifiers are a part of the decl-specifier-seq of a name’s declaration syntax. Together with the scope of the name, they control two independent properties of the name: Its storage duration and its linkage.
auto - automatic storage duration.(until C++11)
register - automatic storage duration. Also hints to the compiler to place the object in the processor’s register. (deprecated)(until C++17)
static - static or thread storage duration and internal linkage
extern - static or thread storage duration and external linkage
thread_local - thread storage duration.(since C++11)
Only one storage class specifier may appear in a declaration except that thread_local may be combined with static or with extern (since C++11)
这个关键字用来再变量声明的时候进行说明的一个语法,与作用域修饰符一起来决定变量的存储周期和作用域,可以与static和extern复合修饰变量。
经过这个关键字定义的变量自动就成为了与线程关联的本地变量,每个线程都有一份拷贝,在线程创建时创建变量,线程销毁时析构变量。示例如下:
thread_local int j = 0;
void foo()
{
m.lock();
j++; // j is now 1, no matter the thread. j is local to this thread.
m.unlock();
}
void func()
{
j = 0;
std::thread t1(foo);
std::thread t2(foo);
t1.join();
t2.join();
// j still 0. The other "j"s were local to the threads
}