`
mbgodi
  • 浏览: 22882 次
  • 性别: Icon_minigender_2
  • 来自: 武汉
社区版块
存档分类
最新评论

Win32多线程编程(5) ― 线程局部存储

 
阅读更多

Win32多线程编程(5) ― 线程局部存储
2010年08月11日
  预留内存携带附加信息的设计
  有时候,将数据与一个对象的实例关联起来是很有帮助的。这种设计要求预留一定的内存,一倍特定附加数据的存储。
  通过调用SetWindowWord或SetWindowLong函数将数据与一个指定的窗口关联起来,数据保存在窗口附加内存块中。窗口内存块即是一种窗口对象(HWND)的附加数据(window extra bytes),参考WNDCLASS.cbWndExtra字段(Specifies the number of extra bytes to allocate following the window instance.)。
  这种预留附加的设计,在MFC中处处可见。对于下拉选择列表(CComboBox)、下拉列表框、列表视图和树控件,我们不光希望其能显示条目内容(item text),还希望每个条目能够携带附加信息,即存储额外的关联数据(item data),以备不时之需。这四个控件都提供了SetItemData/GetItemData接口,供用户储存关联数据。存储的数据为DWORD值类型,可以是简单的数值,也可以存储指针。
  线程消息队列和_ptiddata
  我们在编写第一个SDK窗口程序时,就接触到了消息这一重要概念。实际上,消息队列是一种线程私有数据,每一个Windows程序的UI(CUI/GUI)线程都维持了一个消息队列。GetMessage、TranslateMessage、DispatchMessage等对消息的操作都是与调用线程的消息队列息息相关。PostThreadMessage是线程消息投递函数,它向一个指定ID(idThread)的线程发送一条消息,然后不等处理立即返回。这个API在多线程架构程序中非常有用。PostQuitMessage是结束线程运行,相当于nExitCode作为WM_QUIT消息参数调用PostThreadMessage。调用线程收到该消息后即ExitThread,故该函数一般用来响应WM_DESTROY消息。
  尽管秉持封装的原则,我们极力强调避免使用全局变量,但全局变量对于进程级和线程级的系统统筹管理却是非常有用。除了消息队列这种系统内置的线程私有数据外,Windows提供了线程局部存储系统(TLS,Thread Local Storage),为用户提供了存储与线程关联数据的接口。前面提到的_beginthreadex中分配的_ptiddata(pointer to per-thread data),即使用了TLS。_ptiddata为Windows平台的多线程程序中,strtok、strerror(errno)等依赖全局变量或静态变量的CRT函数的实现提供了有效的解决方案。
  Win32线程局部存储系统
  用于管理 TLS 的数据结构是很简单的,Windows仅为系统中的每一个进程维护一个位数组,再为该进程中的每一个线程申请一个同样长度的数组空间,如下图所示。
  
  在Windbg中,可以窥探TEB中的TLS数据结构。
  lkd> dt _teb
  nt!_TEB
  +0x02c ThreadLocalStoragePointer : Ptr32 Void
  +0xe10 TlsSlots         : [64] Ptr32 Void
  +0xf10 TlsLinks         : _LIST_ENTRY 
  +0xf94 TlsExpansionSlots : Ptr32 Ptr32 Void
  typedef struct _TEB// 66 elements, 0xFB8 bytes (sizeof)
  {
  // ……
  /*0x02C*/     VOID*        ThreadLocalStoragePointer;
  // ……
  /*0xE10*/     VOID*        TlsSlots[64];
  /*0xF10*/     struct _LIST_ENTRY TlsLinks; // 2 elements, 0x8 bytes (sizeof)
  // ……
  /*0xF94*/     VOID**       TlsExpansionSlots;
  // ……
  }TEB, *PTEB;
  当一个线程被创建时,Windows就会在进程地址空间中为该线程分配一个长度为TLS_MINIMUM_AVAILABLE的数组,数组成员的值都被初始化为 0。在内部,系统将此数组与该线程关联起来,保证只能在该线程中访问此数组中的数据。如上图所示,每个线程都有它自己的数组,数组成员可以存储任何数据。
  运行在系统中的每一个进程都有上图所示的一个位数组。位数组的成员是一个标志,每个标志的值被设为FREE或INUSE,指示了此标志对应的数组索引是否在使用中。Windows 保证至少有TLS_MINIMUM_AVAILABLE(定义在WinNT.h文件中)个标志位可用。
  动态使用TLS典型步骤如下。
  (1)主线程调用TlsAlloc函数为线程局部存储分配索引,函数原型如下。
  DWORDTlsAlloc(VOID);
  TlsAlloc为我们预订了一个索引。如果TlsAlloc返回的索引为3,那等于说索引3已经被我们预订了,无论是进程中当前正在运行的线程,还是今后可能会创建的线程,都不能再使用索引3。
  (2)每个线程调用TlsSetValue和TlsGetValue设置或读取线程数组中的值,这两个函数的原型如下。
  BOOLTlsSetValue(
  DWORDdwTlsIndex,  // TLS index
  LPVOIDlpTlsValue  // value to store
  );
  LPVOIDTlsGetValue(
  DWORDdwTlsIndex   // TLS index
  );
  (3)主线程调用TlsFree释放局部存储索引。函数的惟一参数是TlsAlloc返回的索引。
  BOOLTlsFree(
  DWORDdwTlsIndex   // TLS index
  );
  MFC中的线程局部存储
  如果你需要大量的数据贯穿一个线程,普通的TLS索引一个值就会变得不实用,Windows的TLS只允许用户保存一个32位的指针。如果需要用户保存任意类型的数据(包含整个类)。这个任意大小的数据所占的内存通常是在进程的堆中分配,所以当用户释放全局索引时,系统必须将每个线程内此数据占用的内存释放掉,这就要求系统把为各线程分配的内存都记录下来。较好的方法是将各个私有数据的首地址用一个链表连在一起,释放全局索引时只要遍历此链表,就可以逐个释放线程私有数据占用的空间了。
  例如,有下面一个存放线程私有数据的数据结构。
  struct CThreadData
  {
  CThreadData* pNext; // 指向下一个线程的CThreadData结构的指针
  LPVOIDpData;       // 指向真正的线程私有数据的指针
  };
  指针 pData指向为线程分配的内存的首地址,指针pNext将各线程的数据连在了一起。这实际上是一种二级指针的分槽存储。MFC的线程局部存储类CThreadLocal即实现了二级指针的分槽存储。
  MFC框架的状态信息也是理解的难点,包括模块状态AFX_MODULE_STATE、线程状态_AFX_THREAD_STATE和模块线程状态AFX_MODULE_THREAD_STATE。这些线程级别的全局状态维持即使用了线程局部存储(TLS)。参考李久进著作的《MFC深入浅出》第九章《MFC的状态》。 由于MFC广泛地应用了线程局部存储,故在MFC下,使用线程必须格外小心。许多MFC对象仅在创建它们的线程内运作。一般地,具有句柄映射的任何对象都不能从其他线程访问该对象。例如,模块线程状态AFX_MODULE_THREAD_STATE中的CHandleMap* m_pmapHWND映射记录了MFC线程中创建的CWnd对象实例与内核窗口句柄(HWND)之间的映射消息。内核窗口句柄是可以进程访问级别,因此可跨线程访问。但是试图传递CWnd对象实例以期跨线程操作,往往失败。因为另一个引用线程并未像创建线程那样维系一个映射,所以当需要CWndàHWND以执行API操作时,往往找不到其所指窗口。
  针对以上问题,通常优先传送句柄,避免在线程之间传送MFC对象。在引用线程中将其转换为临时MFC对象。例如,假设线程 A创建一个CWnd对象。线程A并不将对象传送给线程B,而将该对象的m_hWnd成员传送给线程B。于是,线程B可以调用CWnd::FromHandle,以创建一个临时的CWnd对象。如果线程B需要更持久的连接,就可以使用Attach方法,在窗口及其CWnd对象之间建立持久的关联。
  另外的一个常见问题是MFC对象访存的线程安全性问题。MFC对象不会自动在不同的线程之间做出判断。所以,如果两个线程试图同时访问同一个CString类的对象,结果可能受到严重破坏。只有防止来自有冲突的MFC对象的线程。通常,这将需要使用前面提到的同步机制,以保证多线程数据交换的一致性。
  参考:
  《为什么要用TLS》
  《WIN32下线程和窗口的数据绑定》
分享到:
评论

相关推荐

    《Windows高级编程指南(第三版)》(含PASCAL例子)

    本书以详尽资料和大量的范例深入讨论了有关32位Windows编程的高级问题,包括进程和线程的管理、Win32的内存管理、消息处理、动态链接库、文件系统和设备输入输出、结构化异常处理、Unicode等,并重点讨论了Windows ...

    Windows程序设计(第2版)王艳_源代码

     3.3 设计自己的线程局部存储   3.4 设计线程类——CWinThread   3.5 【实例】多线程文件搜索器  第4章 Windows图形界面   4.1 了解窗口   4.2 第一个窗口程序  . 4.3 一个“简陋”的打字程序  ...

    Python核心编程第二版

     11.8.1 全局变量与局部变量   11.8.2 globa语句   11.8.3 作用域的数字   11.8.4 闭包   11.8.5 作用域和lambda   11.8.6 变量作用域和名称空间   11.9 *递归   11.10 生成器   11.10.1 ...

    Delphi5开发人员指南

    11.3.1 线程局部存储 314 11.3.2 线程同步 317 11.4 一个多线程的示范程序 325 11.4.1 用户界面 326 11.4.2 搜索线程 330 11.4.3 调整优先级 334 11.5 多线程与数据库 335 11.6 多线程与图形处理 340 11.7 总结 343 ...

    寒江独钓-Windows内核安全编程(高清完整版).part3

    2.6.2 函数的多线程安全性 34 2.6.3 代码的中断级 36 2.6.4 WDK中出现的特殊代码 37 练习题 38 第3章 串口的过滤 40 3.1 过滤的概念 41 3.1.1 设备绑定的内核API之一 41 3.1.2 设备绑定的内核API之二 43 3.1.3 生成...

    Windows编程循序渐进.part2

    15.2.2 实例:多线程环境下的数据共享 278 15.3 内核对象与等待函数 280 15.3.1 内核对象 280 15.3.2 等待函数 281 15.4 事件内核对象 283 15.4.1 基本原理 283 15.4.2 实例:使用事件内核对象示例 284 15.5...

    Windows编程循序渐进.part3

    15.2.2 实例:多线程环境下的数据共享 278 15.3 内核对象与等待函数 280 15.3.1 内核对象 280 15.3.2 等待函数 281 15.4 事件内核对象 283 15.4.1 基本原理 283 15.4.2 实例:使用事件内核对象示例 284 15.5...

    Python核心编程第二版(ok)

    Python核心编程第二版(ok) 第1部分 Python核心  第1章 欢迎来到Python世界   1.1 什么是Python   1.2 起源   1.3 特点   1.3.1 高级   1.3.2 面向对象   1.3.3 可升级   1.3.4 可扩展   ...

    mfc教程(word版)

    9.3 线程局部存储机制和状态的实现 172 9.3.1 CThreadSlotData和_afxThreadData 173 9.3.1.1 CThreadSlotData的定义 173 9.3.1.2 CThreadSlotData的一些数据成员 174 9.3.1.3 _afxThreadData 175 9.3.2 线程状态_...

    MFC-李进九 电子书籍完整版

    9.3 线程局部存储机制和状态的实现 172 9.3.1 CThreadSlotData和_afxThreadData 173 9.3.1.1 CThreadSlotData的定义 173 9.3.1.2 CThreadSlotData的一些数据成员 174 9.3.1.3 _afxThreadData 175 9.3.2 线程状态_...

    易语言入门 易筋经前三章

    1.5.3 子程序的静态局部变量 1.6 自定义数据类型 1.6.1[例]黑客帝国屏保 1.6.2 自定义数据类型的内存存储 1.7 数组 1.7.1 数组的维数 1.7.2 数组的排序 1.7.2.1冒泡排序 1.7.2.2 选择排序 1.7....

    《易精经》前三章 PDF

    1.5.3 子程序的静态局部变量 1.6 自定义数据类型 1.6.1[例]黑客帝国屏保 1.6.2 自定义数据类型的内存存储 1.7 数组 1.7.1 数组的维数 1.7.2 数组的排序 1.7.2.1冒泡排序 1.7.2.2 选择排序 1.7....

    C#微软培训资料

    17.2 文件存储管理 .217 17.3 读 写 文 件 .222 17.4 异步文件操作 .227 17.5 小 结 .234 第十八章 高 级 话 题 .235 18.1 注册表编程 .235 18.2 在 C #代码中调用 C++和 VB 编写的组件 .240 18.3 版 ...

Global site tag (gtag.js) - Google Analytics