线程本地化存储(Thread Local Storage)示例2则

来源:互联网 发布:录音整理软件 编辑:程序博客网 时间:2024/06/05 13:21

TLS:Thread Local Storage

  TLS全称为Thread Local Storage,是Windows为解决一个进程中多个线程同时访问全局变量而提供的机制。TLS可以简单地由操作系统代为完成整个互斥过程,也可以由用户自己编写控制信号量的函数。当进程中的线程访问预先制定的内存空间时,操作系统会调用系统默认的或用户自定义的信号量函数,保证数据的完整性与正确性。

线程区域储存空间 (TLS)

多线程程序中的整体变量(以及任何被配置的内存)被程序中的所有线程共享。在一个函数中的局部静态变量也被使用函数的所有线程共享。一个函数中的局部动态变量是唯一于各个线程的,因为它们被储存在堆栈上,而每个线程有它自己的堆栈。

对各个线程唯一的持续性储存空间有存在的必要。例如,我在本章前面提到过的C中的strtok函数要求这种型态的储存空间。不幸的是,C语言不支持这类储存空间。但是Windows中提供了四个函数,它们实作了一种技术来做到这一点,并且Microsoft对C的扩充语法也支持它,这就叫做线程区域储存空间。

下面是API工作的方法:

首先,定义一个包含需要唯一于线程的所有数据的结构,例如:

typedef struct        {                   int a ;                   int b ;        }        DATA, * PDATA ;        

主线程呼叫TlsAlloc获得一个索引值:

dwTlsIndex = TlsAlloc () ;        

这个值可以储存在一个整体变量中或者通过参数结构传递给线程函数。

线程函数首先为该数据结构配置内存,并使用上面所获得的索引值呼叫TlsSetValue:

TlsSetValue (dwTlsIndex, GlobalAlloc (GPTR, sizeof (DATA)) ;        

该函数将一个指标和某个线程及某个线程索引相关联。现在,任何需要使用这个指标的函数(包括最初的线程函数本身)都可以包含如下所示的程序代码:

PDATA pdata ;        ...        pdata = (PDATA) TlsGetValue (dwTlsIndex) ;        

现在函数可以设定或者使用pdata->a和pdata->b了。在线程函数终止以前,它释放配置的内存:

GlobalFree (TlsGetValue (dwTlsIndex)) ;        

当使用该数据的所有线程都终止之时,主线程将释放索引:

TlsFree (dwTlsIndex) ;        

这个程序刚开始可能令人有些迷惑,因此如果能看一看如何实作线程区域储存空间可能会有帮助(我不知道Windows实际上是如何实作的,但下面的方案是可能的)。首先,TlsAlloc可能只是配置一块内存(长度为0)并传回一个索引值,即指向这块内存的一个指针。每次使用该索引呼叫TlsSetValue时,通过重新配置将内存块增大8个字节。在这8个字节中储存的是呼叫函数的线程ID(通过GetCurrentThreadId来获得)以及传递给TlsSetValue函数的指标。TlsSetValue简单地使用线程ID来搜寻操作系统管理的线程区域储存空间地址表,然后传回指标。TlsFree将释放内存块。所以您看,这可能是一件容易得可以由您自己来实作的事情。不过,既然已经有工具为您做好了这些工作,那也不错。

Microsoft对C的扩充功能使这件工作更加容易。只要在要对每个线程都保留不同内容的变量前加上__declspec (thread)就好了。对于任何函数的外部静态变量,则为:

__declspec (thread) int iGlobal = 1 ;        

对于函数内部的静态变量,则为:

__declspec (thread) static int iLocal = 2 ;        


//////////////////////////////////////////////////////////////////////////

以下的描述及示例摘录自网上,但其内容经过了整理和编辑。


“Thread Local Storage的功用是什么呢?它主要是为了避免多个线程同时访存同一全局变量或者静态变量时所导致的冲突,尤其是多个线程同时需要修改这一变量时。为了解决这个问题,我们可以通过TLS机制,为每一个使用该全局变量的线程都提供一个变量值的副本,每一个线程均可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。而从全局变量的角度上来看,就好像一个全局变量被克隆成了多份副本,而每一份副本都可以被一个线程独立地改变。”

“TLS也可以用于线程同步机制,它能够被用来解决多线程中的对同一变量的访问冲突。在普通的同步机制中,是通过对象加锁来实现多个线程对同一变量的安全访问的。这时该变量是多个线程共享的,使用这种同步机制需要很细致地分析在什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放该对象的锁等等很多。所有这些都是因为多个线程共享了资源造成的,同时,加同步锁也是一个可能会影响性能的瓶颈。TLS从另一个角度来解决多线程的并发访问,它为每一个线程维护一个和该线程绑定的变量的副本,从而隔离了多个线程的数据,每一个线程都拥有自己的变量副本,因而也就没有必要对该变量进行同步了。TLS提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的整个变量封装进TLS,或者把该对象的特定于线程的状态封装进TLS。”

示例1:直接利用Win32 API实现TLS

#include "stdafx.h"
#include 
"windows.h"
#include 
"iostream"

using namespace std;

typedef 
struct {
    DWORD dwFrequency;
    DWORD dwDuration;
}
 soundParams;

DWORD g_nTlsIndex; 
//!!

void ParamsAlloc()
{
    soundParams 
*params;
    
params=(soundParams*)new soundParams;        
    
    
params->dwFrequency=GetTickCount() & 0x00000FFF;
    
params->dwDuration=100;

    cout
<<"使用频率:"<<params->dwFrequency<<"\t延时:"<<params->dwDuration<<endl;

    TlsSetValue(g_nTlsIndex,
params); //!!
}


void ParamsFree()
{
    soundParams 
*params;
    
params=(soundParams*)TlsGetValue(g_nTlsIndex); //!!

    delete 
params;
}


void ToBeep(void)
{
    soundParams 
*params;
    
params=(soundParams*)TlsGetValue(g_nTlsIndex); //!!
    ::Beep(params->dwFrequency,params->dwDuration);
}


void SoundThread()
{
    ParamsAlloc();

    
for (DWORD i=0;i<8;i++{
        ToBeep();
        Sleep(
1000);
    }


    ParamsFree();
}


int _tmain(int argc,_TCHAR *argv[])
{
    HANDLE soundHandles[
3];
    DWORD dwThreadID;
    DWORD dwCount;

    g_nTlsIndex
=TlsAlloc(); //!!
    
   
for(dwCount=0;dwCount<3;dwCount++{
        soundHandles[dwCount]
=CreateThread(NULL,0,
            (LPTHREAD_START_ROUTINE)SoundThread,
0,0,
            
&dwThreadID);

        Sleep(
1500);
    }


    WaitForMultipleObjects(
3,soundHandles,TRUE,INFINITE);

    TlsFree(g_nTlsIndex); 
//!!

    
return 0;
}

示例2:使用编译器支持实现TLS

#include "stdafx.h"
#include 
"windows.h"
#include 
"iostream"

using namespace std;

typedef 
struct {
    DWORD dwFrequency;
    DWORD dwDuration;
}
 soundParams;

__declspec(thread) soundParams *g_pParams; //!!

void SoundThread()
{
    g_pParams
=(soundParams*)new soundParams;
    g_pParams
->dwFrequency=GetTickCount() & 0x00000FFF;
    g_pParams
->dwDuration=100;

    cout
<<"使用频率:"<<g_pParams->dwFrequency<<"\t延时:"<<g_pParams->dwDuration<<endl;

    
for(DWORD i=0;i<8;i++{
        ::Beep(g_pParams
->dwFrequency,g_pParams->dwDuration);
        Sleep(
1000);
    }


    delete g_pParams;
}


int _tmain(int argc,_TCHAR *argv[])
{
    HANDLE soundHandles[
3];
    DWORD dwThreadID;
    DWORD dwCount;

    
for (dwCount=0;dwCount<3;dwCount++{
        soundHandles[dwCount]
=CreateThread(NULL,0,
            (LPTHREAD_START_ROUTINE)SoundThread,
0,0,
            
&dwThreadID);

        Sleep(
1500);
    }


    WaitForMultipleObjects(
3,soundHandles,TRUE,INFINITE);

    
return 0;
}
原创粉丝点击