一、什么是线程的局部存储
在前面提到多线程或者多进程编程中,都会提到一个资源共享的问题。现在可以知道通过使用各种锁来控制数据的共同访问资源的问题。但在实际情况中还会有一种场景,就是每个线程都有自己的相同的类似的资源,这样每个线程都可以控制自己的资源。就如好多孩子一起捡豆子,每人有一个盛豆子的容器一样,每个孩子把自己的豆子放到自己的容器中去,最后凭谁的多来判断胜负。
线程局部存储的意思和上面的例子差不多,其实就是多个线程中的变量你是你的我是我的,咱们互不搭界,互不侵犯,最典型的就是VC中的Getlasterror这个函数,当然它做的也不是特别好。
尤其在多个线程使用同一个线程函数时,这种情况会更普遍,线程的局部存储在不同的平台下会有不同的方法,下面分别介绍。
二、Linux平台
在Linux平台下提供了下面的几个API接口来实现线程的局部存储:
1、创建KEY
int pthread_key_create(pthread_key_t * key, void (* destructor)(void*));
2、删除KEY,注意只是释放线程使用权,并在相关线程置位,并未释放资源
int pthread_key_delete(pthread_key_t key);
3、由KEY取值
void * pthread_getspecific(pthread_key_t key);
4、由KEY设值
int pthread_setspecific(pthread_key_t key, const void * value);
5、GCC提供的关键字__thread来定义变量
下面看一个简单的例子:
#include <pthread.h>
#include <iostream>
static pthread_key_t key;
static pthread_once_t once = PTHREAD_ONCE_INIT;
__thread int local = 9;
int value = 100;
static void createkey(void)
{
pthread_key_create(&key, NULL);
return ;
}
void* threadfunc(void* param)
{
pthread_once(&once, createkey);
if (pthread_getspecific(key) == NULL)
{
pthread_setspecific(key, &value);
}
local++;
local++;
int * d = (int*)pthread_getspecific(key);
std::cout<<"thread is is:"<<(void*)pthread_self()<<"value is:"<<* d<<std::endl;
std::cout<<"thread is is:"<<(void*)pthread_self()<<"local is:"<<local<<std::endl;
return 0;
}
int main(){
pthread_t p1,p2;
pthread_create(&p1,NULL,threadfunc,(void *)0);
pthread_create(&p2,NULL,threadfunc,(void *)1);
pthread_join(p1,NULL);
pthread_join(p2,NULL);
return 0;
}
执行结果:
thread is is:139655031064320value is:100
thread is is:139655031064320local is:11
thread is is:139655039457024value is:100
thread is is:139655039457024local is:11
三、 Windows平台
每个平台虽然都有不同,但原理却基本相同,在Windows中,每个线程创建是分得一个TLS索引数组,其实就是一个lpvoid的指针数组,一般来说为了接口的方便性,Windows上经常把一些指针声明成通用的void*,这样方便转来转去,不过这也方便崩来崩去(开个玩笑)。其实这个数组就是为了访问线程信息块TIB的方便,每个线程都会维护自己的栈和一些相关的资源。而线程内部的局部存储就是这个意思,它会被TLS数组索引。
实际上可以简单理解一个有一个指针,这个指针被每个线程分别持有,但是它指向的相关内存的内容不同,这样,在每次操作此指针时,虽然名字相同,但是实际操作的数据并不同,这样就会产生局部存储这种功能。它可以通过下面的几步来完成:
1、主进程内调用TlsAlloc()函数,得到序号:
DWORD index = TLSAlloc();
2、分配线程存储空间
void* pvalue = LocalAlloc(LPTR,sizeof(int));
再通过TlsSetValue()函数把这块地址存储上面的分配的索引中:
TlsSetValue( index, pvalue);
3 、操作
LPVOID lpvData = TlsGetValue(index);
- lpvData = 8; //应用该线程局部存储
4、释放
不再需要上述线程局部静态变量,可以使用LocalFree()函数来释放资源,主进程使用TlsFree()函数释放索引资源。
LocalFree((HLOCAL) pvalue );
TlsFree(index);
当然Windows平台上的编译器也支持关键字来定义线程局部存储变量:
__declspec( thread ) int var_name;
这些使用要非常注意不同的Windows版本中的不同的情况,小心应付。在高版本或者服务平台上可能无法使用或者得换相关的头文件等。
看一下下面的大概例子(有些平台无法使用,所以只给了一个原型):
#include <Windows.h>
#include <process.h>
UINT WINAPI ThreadFunc(LPVOID params)
{
//测试环境不支持下面的这个操作
//void* pvalue = LocalAlloc(LPTR,sizeof(float));
int value = 9;
::TlsSetValue(index, (LPVOID)value);
std::cout << "cur thread id: " << ::GetCurrentThreadId() << "cur value:" << ::TlsGetValue(index) << std::endl;;
return 0;
}
int win_local()
{
HANDLE handle[5];
//得到TLS索引
index = ::TlsAlloc();
//开启线程
for (int i = 0; i < 5; i++)
{
handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, NULL);
}
//等待工作线程
::WaitForMultipleObjects(5, handle, TRUE, INFINITE);
for (int i = 0; i < 5; i++)
{
::CloseHandle(handle[i]);
}
//释放TLS
::TlsFree(index);
return 0;
}
执行结果:
cur thread id: cur thread id: 130828cur thread id: cur value:cur thread id: 79080102696104344cur value:cur value:0000000000000009
cur thread id: 136032cur value:cur value:0000000000000009
0000000000000009
0000000000000009
0000000000000009
四、C++11 Thread的线程本地存储(Thread Local Storage)
与TLS(thread local storage)类似,在c++11中提供了一个thread_local的声明符,用它声明的变量变量每一个线程都有自己的该全局变量的实例(instance),该实例的变量名就是全局变量名称。
//以前:
int t = 0;
std::mutex m;
void test_mutex()
{
m.lock();
t++;
std::cout << "tlocal value is:" << t << std::endl;
m.unlock();
}
void test_func()
{
t = 0;
std::thread t1(test_mutex);
std::thread t2(test_mutex);
std::cout << "tlocal value is:" << t << std::endl;
t1.join();
t2.join();
}
//现在看:
thread_local int tlocal = 0;
void test_thread_local()
{
m.lock();
tlocal++;
std::cout << "tlocal value is:" << tlocal << std::endl;
m.unlock();
}
void test_local_func()
{
tlocal = 0;
std::thread t1(test_thread_local);
std::thread t2(test_thread_local);
Sleep(100);//目的是为了让出主线程,测试一下局部对象的影响性
std::cout << "tlocal value is:" << tlocal << std::endl;
t1.join();
t2.join();
}
int main()
{
test_func();
test_local_func();
}
执行结果:
t value is:0
t value is:1
t value is:2
tlocal value is:1
tlocal value is:1
tlocal value is:0
这个结果可以看到,在使用锁的情况下,数据顺序升级,但如果使用本地变量,本地则自动升级,如果大家感兴趣,可以多增加几个线程就发现了,或者在自己的线程中多调用几次,对比一下,会更清楚。
五、总结
线程的局部变量一般来说可以用三种类型的变量来定义,即具名空间下的全局变量、类的静态成员变量和局部变量。其实线程局部存储的引入是和c++中的变量生存周期(automatic,static,dynamic,thread)有关系,其中只有thread_local是具有线程周期的。明白了各种数据类型的定义和关系,才能在实际的应用场景中游刃有余的解决相关的实际问题,每天多学一点,多总结一点,每天多进步一点!
苟日新,日日新!