线程的局部存储

236 篇文章 95 订阅

一、什么是线程的局部存储

在前面提到多线程或者多进程编程中,都会提到一个资源共享的问题。现在可以知道通过使用各种锁来控制数据的共同访问资源的问题。但在实际情况中还会有一种场景,就是每个线程都有自己的相同的类似的资源,这样每个线程都可以控制自己的资源。就如好多孩子一起捡豆子,每人有一个盛豆子的容器一样,每个孩子把自己的豆子放到自己的容器中去,最后凭谁的多来判断胜负。
线程局部存储的意思和上面的例子差不多,其实就是多个线程中的变量你是你的我是我的,咱们互不搭界,互不侵犯,最典型的就是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是具有线程周期的。明白了各种数据类型的定义和关系,才能在实际的应用场景中游刃有余的解决相关的实际问题,每天多学一点,多总结一点,每天多进步一点!
苟日新,日日新!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值