书上说进程内有一索引数组,其大小和该进程中创建的线程的数组大小一样,关于这两个数组,关系到底如何,谁能详细讲一下,我刚在学,还不是很清楚。如果能把TLS实现过程通俗的讲一下,就更好了,我到现在还不是很清楚TLS能用到哪里,有什么用?

解决方案 »

  1.   

    看windows核心编程,有一章专门讲到了这个问题
      

  2.   

    TLS(Thread Local Storage),也就是线程局部存储。线程是内核对象,对于每个线程,系统维护着一个ETHREAD块,ETHREAD的第一项是KTHREAD, KTHREAD结构大致如此:
    Dispather header
    Total user time
    Total kernel time
    ...
    ---->TLS arrray
    ...
    Thread environment block可见每个线程都有着单独的TLS。当线程生成时,系统保留并请空一列存储单元,至少64个,给这个个线程。进程也增加一个对该列单元的索引,索引中的每位(bit)对应每个单元,表示占用或空闲。
    之所以需要另外一个索引表,是因为零值可以合法的放到存储单元中,系统需要用其他方法来表示单元是否被占用。如果有了第二个线程,进程就维护两个索引表。由此类推。当你要使用TLS时,首先要取得一个空的存储单元的号码,然后你就利用这个号码对TLS进行操作,直到你释放这个单元为止。有4个函数可以来操作TLS:DWORD TlsAlloc()                                // 取得一个存储单元
    BOOL  TlsSetValue(DWORD index, PVOID val)      // 存一个指针或长整数
    PVOID TlsGetValue(DWORD index)                  // 取
    BoOL  TlsFree(DWORD index)                      // 放弃一个存储单元
    更详细的内容,你可以参考
    1:"Thread Local Storage", http://msdn2.microsoft.com/en-us/library/ms686749.aspx
    2:"Windows Internals", M. Russinovich and D. Solomon
      

  3.   

    to gomoku ,我摘了书上几句话,对于你说:“如果有了第二个线程,进程就维护两个索引表。由此类推。 ”不太理解,下面是我从书上摘录的原话:
    “用于管理TLS的数据结构是很简单的,Windows仅为系统中的每一个进程维护一个位数组,再为该进程中的每一个线程申请一个同样长度的数组空间。TlsSetValue和TlsGetValue分别用于设置和取得线程数组中的特定成员的值,而它们使用的索引就是TlsAlloc函数的返回值。这就充分说明了进程中唯一的位数组和各线程数组的关系。例如,TlsAlloc返回3,那就说明索引3被此进程中的每一个正在运行的和以后要被创建的线程保存起来,用以访问各自线程数组中对应的成员的值。”我看书上讲得好像就一个位数组,没说有多个索引表啊?
      

  4.   

    http://msdn2.microsoft.com/en-us/library/ms686749.aspx 有一个图,你一看就明白了.
      

  5.   

    我刚刚也看到你提的说法了,"Windows via C++, J. Richter and C. Nasarre"中提到:(大体意思是)TlsAlloc返回3,那就说明索引3被保留了,对于当前线程3被保留,对将来创建的线程3亦被保留。如此一来, 每个进程一个位数组就够了。(我的看法是两种方式都无碍于局部存储的实现,或许我们因该习惯这种实现细节的隐藏,我们更关心线程局部存储功能。)。
      

  6.   

    线程局部存储是特定的,并且从一开始就已经分配并被保留的有限的线程间相互独立的结构特定的(以一个机器字长为单元)存储空间。同时由于本身设计上面的一个特定性,从而在每一个进程当中用于维护一个全局的位数组,用于记录已经被占用的空间位(置),以达到通道的在各线程当中的专用性(一致性)。该“位数组”跟普通的全局内存空间一样,只是操作的专属性(TlsAlloc/TlsFree),从而使得同一进程当中的任意线程在任意进程工作时间内都可以进行“专用”标识,这个专用性由“位数组”的专属操作API来达到保证。
      

  7.   

    个人提议以下几个注意点:
    1.位置在各线程当中形成一个对称性分派,但是需要注意的是,任意同进程内的线程的申请可以占用(保留)某一位置,同样的,任意同进程内的线程也可以释放该位置,这是其中一个注意点。
    2.由于有限性,最多只有1088个,虽然可以达到多线程当中的快速有效的重复访问,但是却不可以滥用,在不非必要使用的时候尽可能不使用,除非使用之后可以解决相应的应用瓶颈。使用的时候尽量公开化,尽可能避开封装,否则会缩小该封装使用范围,特别是一些非单例类型当中避免使用。
    3.由于单元空间的有限性,正如MSDN所说的那样,在已知的不破坏封装性的情况下尽可能使用一个扩展结构(存储其指针到线程局部存储当中)来替代多个线程局部存储空间位置的占用。
      

  8.   

    学习...
    最近也在看TLS方面的内容,我把它理解成类对象,每个类对象只能有一个类的成员的拷贝。
      

  9.   

    比起美国妇女来,WINDOWS就不那么开放了,一个进程被创建的时候,系统就偷偷的为该进程下每个线程建立了一个用于存储数据的"线程存储数组"(长度都相同),并为这个进程建立用于管理线程位数组索引的一个"索引数组"(每个进程只有一个),这些是系统完成的,在CreateProcess函数和 CreateThread函数中是没有体现出来的,所以有点神秘感,以下是我的一些理解:1、每个线程可以通过进程的索引数组的索引访问自己存储数据的"线程存储数组",索引数组成员值有FREE(标志该索引对应下的“每个线程”存储数组成员空闲)和 INUSE(标志该索引对应下下的每个线程存储数组成员已用)线程使用自己的存储数组前通过主线程调用TlsAlloc函数返回一个数组成员值为 FREE(说明线程存储数组该索引成员空闲可用)的索引;
    2、系统使进程索引位数组的长度和每个线程存储数据的位数组长度一样,否则索引位数组如果有100位,而每个线程存储数组有101位,索引数组就没法索引这101位的数据并使用它,长度一样是管理上的需要;
    3、进程的索引数组成员FREE和INUSE的设置是:比如一个新建进程,进程的索引数组a[n]成员值全部是FREE,而全部线程的线程存储数组成员都是空闲,出现某线程首次调用TlsAlloc函数时返回比如索引数组成员第一个a[0]就是FREE的索引,TlsAlloc函数将a[0]值改为 INUSE,该线程使用TlsSet函数将自己的线程存储数组b[0]的成员使用,在a[0]未被TlsFree函数释放前,所用线程的线程存储数组 b[0]都可以使用,不是一个线程用一次存储数组就必须要主线程调用一次TlsAlloc函数;
    4、索引数组可以通过TlsFree函数释放索引(即改a[0]值为FREE)和b[0]空间,保证进程的索引数组不会全部被占用;
    5、象很多内核对象一样"线程存储数组"和"索引数组"也是WINDOWS封闭保护的私处只一,不会让你象自己定义个数组那样直观、直接、明白的使用,而是提供几个TlsAlloc、TlsSetValue、TlsGetValue、TlsFree这样的函数间接使用,所以感觉有点暧昧难懂;
    6、动态使用TLS的步骤:
      (1)主线程调用TlsAlloc函数分配标记线程存储数组未被使用成员的索引A,并将进程索引位数组的该索引成员值由FREE改为INUSE;
      (2)线程使用TlsAlloc函数分配的索引使用TlsSetValue(索引A,要设置的值)设置自己线程存储数组成员的值以便使用,或者用TlsGetValue函数读取成员值使用;  (3)主线程调用TlsFree(索引A)释放索引A和存储数组空间,已保证索引和存储空间的循环使用;