Windows 程序设计(第2版) 3.2.6 线程局部存储

线程局部存储(thread-local storage, TLS)是一个使用很方便的存储线程局部数据的系统。利用TLS机制可以为进程中所有的线程关联若干个数据,各个线程通过由TLS分配的全局索引来访问与 自己关联的数据。这样,每个线程都可以有线程局部的静态存储数据。

用于管理TLS的数据结构是很简单 的,Windows仅为系统中的每一个进程维护一个位数组,再为该进程中的每一个线程申请一个同样长度的数组空间,如图3.9所示。

图3.9  TSL机制在内部使用的数据结构

运行在系统中的每一个进程都有图3.9所示的一个位 数组。位数组的成员是一个标志,每个标志的值被设为FREE或INUSE,指示了此标志对应的数组索引是否在使用中。Windodws保证至少有 TLS_MINIMUM_AVAILABLE(定义在WinNT.h文件中)个标志位可用。

动态使用TLS的典型步骤如下。

(1)主线程调用TlsAlloc函数为线程局部存 储分配索引,函数原型为:

DWORD TlsAlloc(void);  // 返回一个TLS索引

如上所述,系统为每一个进程都维护着一个长度为 TLS_MINIMUM_AVAILABLE的位数组,TlsAlloc的返回值就是数组的一个下标(索引)。这个位数组的惟一用途就是记忆哪一个下标在 使用中。初始状态下,此位数组成员的值都是FREE,表示未被使用。当调用TlsAlloc的时候,系统会挨个检查这个数组中成员的值,直到找到一个值为 FREE的成员。把找到的成员的值由FREE改为INUSE后,TlsAlloc函数返回该成员的索引。如果不能找到一个值为FREE的成 员,TlsAlloc函数就返回TLS_OUT_OF_INDEXES(在WinBase.h文件中定义为-1),意味着失败。

例如,在第一次调用TlsAlloc的时候,系统发 现位数组中第一个成员的值是FREE,它就将此成员的值改为INUSE,然后返回0。

当一个线程被创建时,Windows就会在进程地址 空间中为该线程分配一个长度为TLS_MINIMUM_AVAILABLE的数组,数组成员的值都被初始化为0。在内部,系统将此数组与该线程关联起来, 保证只能在该线程中访问此数组中的数据。如图3.7所示,每个线程都有它自己的数组,数组成员可以存储任何数据。

(2)每个线程调用TlsSetValue和 TlsGetValue设置或读取线程数组中的值,函数原型为:

BOOL TlsSetValue(

  DWORD dwTlsIndex,     // TLS 索引

  LPVOID lpTlsValue                   // 要设置的值

);

LPVOID TlsGetValue(DWORD dwTlsIndex );       // TLS索引

TlsSetValue函数将参数 lpTlsValue指定的值放入索引为dwTlsIndex的线程数组成员中。这样,lpTlsValue的值就与调用TlsSetValue函数的线 程关联了起来。此函数调用成功,会返回TRUE。

调用TlsSetValue函数,一个线程只能改变 自己线程数组中成员的值,而没有办法为另一个线程设置TLS值。到现在为止,将数据从一个线程传到另一个线程的惟一方法是在创建线程时使用线程函数的参 数。

TlsGetValue函数的作用是取得线程数组中 索引为dwTlsIndex的成员的值。

TlsSetValue和TlsGetValue分 别用于设置和取得线程数组中的特定成员的值,而它们使用的索引就是TlsAlloc函数的返回值。这就充分说明了进程中惟一的位数组和各线程数组的关系。 例如,TlsAlloc返回3,那就说明索引3被此进程中的每一个正在运行的和以后要被创建的线程保存起来,用以访问各自线程数组中对应的成员的值。

(3)主线程调用TlsFree释放局部存储索引。 函数的惟一参数是TlsAlloc返回的索引。

利用TLS可以给特定的线程关联一个数据。比如下面 的例子将每个线程的创建时间与该线程关联了起来,这样,在线程终止的时候就可以得到线程的生命周期。整个跟踪线程运行时间的例子的代码如下:

#include <stdio.h>                                   // 03UseTLS工程下

#include <windows.h>            

#include <process.h>

// 利用TLS跟踪线程的运行时间

DWORD g_tlsUsedTime;

void InitStartTime();

DWORD GetUsedTime();

UINT __stdcall ThreadFunc(LPVOID)

{       int i;

         // 初始化开始时间

         InitStartTime();

         // 模拟长时间工作

         i = 10000*10000;

         while(i--){}

         // 打印出本线程运行的时间

         printf(" This thread is coming to end. Thread ID: %-5d, Used Time: %d /n",

                                                                                                       ::GetCurrentThreadId(), GetUsedTime());

         return 0;

}

int main(int argc, char* argv[])

{       UINT uId;

         int i;

         HANDLE h[10];

         // 通过在进程位数组中申请一个索引,初始化线程运行时间记录系统

         g_tlsUsedTime = ::TlsAlloc();

         // 令十个线程同时运行,并等待它们各自的输出结果

         for(i=0; i<10; i++)

         {       h[i] = (HANDLE)::_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, &uId);         }

         for(i=0; i<10; i++)

         {       ::WaitForSingleObject(h[i], INFINITE);

                   ::CloseHandle(h[i]);      }

         // 通过释放线程局部存储索引,释放时间记录系统占用的资源

         ::TlsFree(g_tlsUsedTime);

         return 0;

}

// 初始化线程的开始时间

void InitStartTime()

{       // 获得当前时间,将线程的创建时间与线程对象相关联

         DWORD dwStart = ::GetTickCount();

         ::TlsSetValue(g_tlsUsedTime, (LPVOID)dwStart);

}

// 取得一个线程已经运行的时间

DWORD GetUsedTime()

{       // 获得当前时间,返回当前时间和线程创建时间的差值

         DWORD dwElapsed = ::GetTickCount();

         dwElapsed = dwElapsed - (DWORD)::TlsGetValue(g_tlsUsedTime);

         return dwElapsed;

}

GetTickCount函数可以取得 Windows从启动开始经过的时间,其返回值是以毫秒为单位的已启动的时间。

一般情况下,为各线程分配TLS索引的工作要在主线 程中完成,而分配的索引值应该保存在全局变量中,以方便各线程访问。上面的例子代码很清除地说明了这一点。主线程一开始就使用TlsAlloc为时间跟踪 系统申请了一个索引,保存在全局变量g_tlsUsedTime中。之后,为了示例TLS机制的特点同时创建了10个线程。这10个线程最后都打印出了自 己的生命周期,如图3.10所示。

3.10  各线程的生命周期

这个简单的线程运行时间记录系统仅提供 InitStartTime和GetUsedTime两个函数供用户使用。应该在线程一开始就调用InitStartTime函数,此函数得到当前时间 后,调用TlsSetValue将线程的创建时间保存在以g_tlsUsedTime为索引的线程数组中。当想查看线程的运行时间时,直接调用 GetUsedTime函数就行了。这个函数使用TlsGetValue取得线程的创建时间,然后返回当前时间和创建时间的差值。

另外用于线程同步的内核对象还有互斥体和信号量,它 们的用法也比较简单,这里就不介绍了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值