windows核心编程指南21.pdf

上传人:qwe****56 文档编号:70013377 上传时间:2023-01-14 格式:PDF 页数:6 大小:301.94KB
返回 下载 相关 举报
windows核心编程指南21.pdf_第1页
第1页 / 共6页
windows核心编程指南21.pdf_第2页
第2页 / 共6页
点击查看更多>>
资源描述

《windows核心编程指南21.pdf》由会员分享,可在线阅读,更多相关《windows核心编程指南21.pdf(6页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。

1、下载第2 1章线程本地存储器有时,将数据与对象的实例联系起来是很有帮助的。例如,窗口的附加字节可以使用S e t Wi n d o w s Wo r d和S e t Wi n d o w L o n g函数将数据与特定的窗口联系起来。可以使用线程本地存储器将数据与执行的特定线程联系起来。例如,可以将线程的某个时间与线程联系起来。然后,当线程终止运行时,就能够确定线程的寿命。C/C+运行期库要使用线程本地存储器(T L S)。由于运行期库是在多线程应用程序出现前的许多年设计的,因此运行期库中的大多数函数是用于单线程应用程序的。函数 s t r t o k就是个很好的例子。应用程序初次调用 s t

2、 r t o k时,该函数传递一个字符串的地址,并将字符串的地址保存在它自己的静态变量中。当你将来调用s t r t o k函数并传递N U L L时,该函数就引用保存的字符串地址。在多线程环境中,一个线程可以调用 s t r t o k,然后,在它能够再次调用该函数之前,另一个线程也可以调用 S t r t o k。在这种情况下,第二个线程会在第一个线程不知道的情况下,让s t r t o k用一个新地址来改写它的静态变量。第一个线程将来调用 s t r t o k时将使用第二个线程的字符串,这就会导致各种各样难以发现和排除的错误。为了解决这个问题,C/C+运行期库使用了T L S。每个线程

3、均被赋予它自己的字符串指针,供s t r t o k函数使用。需要予以同样对待的其他C/C+运行期库函数还有a s c t i m e和g m t i m e。如果你的应用程序需要严重依赖全局变量或静态变量,那么 T L S能够帮助解决它遇到的问题。但是编程人员往往尽可能减少对这些变量的使用,而更多地依赖自动(基于堆栈的)变量和通过函数的参数传递的数据。这样做是很好的,因为基于堆栈的变量总是与特定的线程相联系的。标准的C运行期库一直是由许多不同的编译器供应商来实现和重新实现的。如果 C编译器不包含标准的C运行期库,那么就不值得去购买它。编程员多年来一直使用标准的 C运行期库,并且将会继续使用它

4、,这意味着s t r t o k之类的函数的原型和行为特性必须与上面所说的标准 C运行期库完全一样。如果今天重新来设计C运行期库,那么它就必须支持多线程应用程序的环境,并且必须采取相应的措施来避免使用全局变量和静态变量。在我的软件开发项目中,我总是尽可能避免使用全局变量和静态变量。如果你的应用程序使用全局变量和静态变量,那么建议你务必观察每个变量,并且了解一下它能否改变成基于堆栈的变量。如果打算将线程添加给应用程序,那么这样做可以节省大量时间,甚至单线程应用程序也能够从中得到许多好处。在编写应用程序和D L L时,可以使用本章中介绍的两种T L S方法,即动态T L S和静态T L S。但是,

5、当创建D L L时,这些T L S往往更加有用,因为D L L常常不知道它们链接到的应用程序的结构。不过,当编写应用程序时,你通常知道将要创建多少线程以及如何使用这些线程。然后就可以创造一些临时性的方法,或者最好是使用基于堆栈的方法(局部变量),将数据与创建的每个线程联系起来。不管怎样,应用程序开发人员也能从本章讲述的内容中得到一些启发。21.1 动态T L S若要使用动态T L S,应用程序可以调用一组 4个函数。这些函数实际上是 D L L用得最多的函数。图2 1-1显示了Wi n d o w s用来管理T L S的内部数据结构。图21-1 用于管理T L S的内部数据结构该图显示了系统中

6、运行的线程正在使用的一组标志。每个标志均可设置为F R E E或者I N U S E,表示T L S时隙(s l o t)是否正在使用。M i c r o s o f t保证至少T L S _ M I N I M U M _ AVA I L A B L E位标志是可供使用的。另外,T L S _ M I N I M U M _ AVA I L A B L E在Wi n N T.h中被定义为6 4。Wi n d o w s2 0 0 0将这个标志数组扩展为允许有1 0 0 0个以上的T L S时隙。对于任何一个应用程序来说,这个时隙数量足够了。若要使用动态T L S,首先必须调用T l s A

7、l l o c函数:这个函数命令系统对进程中的位标志进行扫描,并找出一个 F R E E标志。然后系统将该标志从F R E E改为I N U S E,并且T l s A l l o c返回位数组中的标志的索引。D L L(或应用程序)通常将该索引保存在一个全局变量中。这是全局变量作为一个较好选择的情况之一,因为它的值是每个进程而不是每个线程使用的值。如果T l s A l l o c在该列表中找不到 F R E E标志,它就返回 T L S _ O U T _ O F _ I N D E X E S(在Wi n B a s e.h中定义为0 x F F F F F F F F)。当T l s

8、A l l o c第一次被调用时,系统发现第一个标志是F R E E,并将该标志改为I N U S E,同时T l s A l l o c返回0。T l s A l l o c这样运行的概率是9 9%。下面介绍在另外的1%的概率下T l s A l l o c是如何运行的。当创建一个线程时,便分配一个 T L S _ M I N I M U M _ AVA I L A B L E P V O I D值的数组,并将它初始化为0,然后由系统将它与线程联系起来。如图 2 1-1所示,每个线程均得到它自己的数组,数组中的每个P V O I D可以存储任何值。在能够将信息存储在线程的P V O I D数

9、组中之前,必须知道数组中的哪个索引可供使用,这就是前面调用T l s A l l o c所要达到的目的。按照设计概念,T l s A l l o c为你保留了一个索引。如果T l s A l l o c返回索引3,那么就说明目前在进程中运行的每个线程中均为你保留了索引 3,而且在将来创建的线程中也保留了索引3。510计计第四部分动态链接库下载进程线程1引索0引索000000000000000引索1引索1引索2引索2引索3引索3引索4引索4线程2线程本地存储器的位标志:0-(TLS_MINIMUM_AVAILABLE-1)索引TLS_MINIMUM_AVAILABLE-2索引TLS_MINIMU

10、M_AVAILABLE-2索引TLS_MINIMUM_AVAILABLE-2索引TLS_MINIMUM_AVAILABLE-2若要将一个值放入线程的数组中,可以调用T l s S e t Va l u e函数:该函数将一个P V O I D值(用p v T l s Va l u e参数标识)放入线程的数组中由 d w T l s I n d e x参数标识的索引处。P v T l s Va l u e的值与调用T l s S e t Va l u e的线程相联系。如果调用成功,便返回T R U E。线程在调用T l s S e t Va l u e时,可以改变它自己的数组。但是它不能为另一个线

11、程设置 T L S值。我希望有另一个T l s函数能够用于使一个线程将数据存储到另一个线程的数组中,但是不存在这样一个函数。目前,将数据从一个线程传递到另一个线程的唯一方法是,将单个值传递给C r e a t e T h r e a d或_ b e g i n t h r e a d e x,然后该函数将该值作为唯一的参数传递给线程的函数。当调用 T l s S e t Va l u e时,始终都应该传递较早的时候调用的T l s A l l o c函数返回的索引。M i c r o s o f t设计的这些函数能够尽快地运行,在运行时,将放弃错误检查。如果传递的索引是调用T l s A l

12、l o c时从未分配的,那么系统将设法把该值存储在线程的数组中,而不进行任何错误检查。若要从线程的数组中检索一个值,可以调用T l s G e t Va l u e:该函数返回的值将与索引 d w T l s I n d e x处的 T L S时隙联系起来。与 T l s S e t Va l u e一样,T l s G e t Va l u e只查看属于调用线程的数组。还有,T l s G e t Va l u e并不执行任何测试,以确定传递的索引的有效性。当在所有线程中不再需要保留T L S时隙的位置的时候,应该调用T l s F r e e:该函数简单地告诉系统该时隙不再需要加以保留。由

13、进程的位标志数组管理的I N U S E标志再次被设置为F R E E。如果线程在后来调用T l s A l l o c函数,那么将来就分配该I N U S E标志。如果T l s F r e e函数运行成功,该函数将返回T R U E。如果试图释放一个没有分配的时隙,将产生一个错误。使用动态T L S通常情况下,如果 D L L使用T L S,那么当它用 D L L _ P R O C E S S _ AT TA C H标志调用它的D l l M a i n函数时,它也调用T l s A l l o c。当它用D L L _ P R O C E S S _ D E TA C H调用D l l

14、 M a i n函数时,它就调用TlsFree。对TlsSetValue和TlsGetValue的调用很可能是在调用DLL中包含的函数时进行的。将T L S添加给应用程序的方法之一是在需要它时进行添加。例如,你的D L L中可能有一个运行方式类似s t r t o k的函数。第一次调用这个函数时,线程传递一个指向4 0字节的结构的指针。必须保存这个结构,这样,将来调用函数时就可以引用它。可以像下面这样对你的函数进行编码:第 21章 线程本地存储器计计511下载如果你的应用程序的线程从来不调用 M y F u n c t i o n,那么也就从来不用为该线程分配内存块。6 4个T L S位置看来

15、超出了你的需要。但是请记住,应用程序可以动态地链接到若干个 D L L。第一个D L L可以分配1 0个T L S索引,第二个D L L可以分配5个T L S索引,依此类推。减少你需要的T L S索引始终是个好思路。减少所用T L S索引的最好的办法,是采用前面的代码中的M y F u n c t i o n使用的那种方法。当然,可以在多个T L S索引中保存全部4 0个字节,但是这样做不仅很浪费,而且使数据的操作很困难。相反,应该为数据分配一个内存块,并且像 M y F u n c t i o n那样,只将指针保存在单个T L S索引中。正如前面讲过的那样,Windows 2000允许设置1

16、 0 0 0多个T L S时隙。M i c r o s o f t增加了时隙的数量,因为许多编程人员对时隙的使用采取一种只顾自己不顾其他的态度,不给其他D L L分配时隙,从而导致它们运行失败。前面介绍T l s A l l o c函数时,只描述了该函数能够实现的 9 9%的功能。为了帮助了解剩下的1%的功能,让我们观察一下下面的代码段:在这个代码运行之后,你认为p v S o m e Va l u e将包含什么信息呢?包含1 2 3 4 5?答案是包含0。T l s A l l o c在返回之前,要遍历进程中的每个线程,在每个线程的数组中的新分配索引处放入 0。512计计第四部分动态链接库下

17、载这是很不错的,因为应用程序可能调用 L o a d L i b r a r y来加载D L L,而D L L则调用T l s A l l o c来分配索引,然后,线程调用F r e e L i b r a r y来删除D L L。D L L应该通过调用T l s F r e e来释放它的索引,但是谁知道D L L的代码将哪些值放入线程的数组中呢?接着,线程调用 L o a d L i b r a r y,将另一个D L L加载到内存中。该 D L L启动时也调用 T l s A l l o c,并获得与前面的 D L L相同的索引。如果T l s A l l o c没有为进程中的所有线程设置

18、返回的索引,那么线程就可能看到一个老的值,而代码则无法正确地运行。例如,这个新D L L可以查看对T l s G e t Va l u e函数的调用是否曾经为一个线程分配了内存,就像前面的代码段显示的那样。如果 T l s A l l o c没有删除每个线程的数组项目,那么第一个 D L L的老数据仍然可以使用。如果线程调用 M y F u n c t i o n,那么M y F u n c t i o n就会认为内存块已经分配,并且调用m e m c p y函数,将新数据拷贝到它所认为的内存块中。这可能造成灾难性的后果,不过,幸好T l s A l l o c会对数组元素进行初始化,使这样的

19、灾难永远不会发生。21.2 静态T L S与动态T L S一样,静态T L S也能够将数据与线程联系起来。但是,静态 T L S在代码中使用起来要容易得多,因为不必调用任何函数就能够使用它。比如说,你想要将起始时间与应用程序创建的每个线程联系起来。只需要将起始时间变量声明为下面的形式:_ _ d e c l s p e c(t h r e a d)的前缀是M i c r o s o f t添加给Visual C+编译器的一个修改符。它告诉编译器,对应的变量应该放入可执行文件或 D L L文件中它的自己的节中。_ _ d e c l s p e c(t h r e a d)后面的变量必须声明为函

20、数中(或函数外)的一个全局变量或静态变量。不能声明一个类型为_ _d e c l s p e c(t h r e a d)的局部变量。这不应该是个问题,因为局部变量总是与特定的线程相联系的。我将前缀g t _用于全局T L S变量,而将s t _用于静态T L S变量。当编译器对程序进行编译时,它将所有的T L S变量放入它们自己的节,这个节的名字是.t l s。链接程序将来自所有对象模块的所有.t l s节组合起来,形成结果的可执行文件或 D L L文件中的一个大的.t l s节。为了使静态T L S能够运行,操作系统必须参与其操作。当你的应用程序加载到内存中时,系统要寻找你的可执行文件中的

21、.t l s节,并且动态地分配一个足够大的内存块,以便存放所有的静态T L S变量。你的应用程序中的代码每次引用其中的一个变量时,就要转换为已分配内存块中包含的一个内存位置。因此,编译器必须生成一些辅助代码来引用该静态 T L S变量,这将使你的应用程序变得比较大而且运行的速度比较慢。在 x86 CPU上,将为每次引用的静态T L S变量生成3个辅助机器指令。如果在进程中创建了另一个线程,那么系统就要将它捕获并且自动分配另一个内存块,以便存放新线程的静态T L S变量。新线程只拥有对它自己的静态 T L S变量的访问权,不能访问属于其他线程的T L S变量。这就是静态T L S变量如何运行的基

22、本情况。现在让我们来看一看 D L L的情况。你的应用程序很可能要使用静态T L S变量,并且链接到也想使用静态 T L S变量的一个D L L。当系统加载你的应用程序时,它首先要确定应用程序的.t l s节的大小,并将这个值与你的应用程序链接的D L L中的任何.t l s节的大小相加。当在你的进程中创建线程时,系统自动分配足够大的内存块来存放应用程序需要的所有T L S变量和所有隐含链接的D L L。第 21章 线程本地存储器计计513下载下面让我们来看一下当应用程序调用L o a d L i b r a r y,以便链接到也包含静态T L S变量的一个D L L时,将会发生什么情况。系统

23、必须查看进程中已经存在的所有线程,并扩大它们的 T L S内存块,以便适应新D L L对内存的需求。另外,如果调用F r e e L i b r a r y来释放包含静态T L S变量的D L L,那么与进程中的每个线程相关的的内存块应该被压缩。对于操作系统来说,这样的管理任务太重了。虽然系统允许包含静态 T L S变量的库在运行期进行显式加载,但是T L S数据没有进行相应的初始化。如果试图访问这些数据,就可能导致访问违规。这是使用静态T L S的唯一不足之处。当使用动态 T L S时,不会出现这个问题。使用动态T L S的库可以在运行期进行加载,并且可以在运行期释放,根本不会产生任何问题。514计计第四部分动态链接库下载

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 技术资料 > 其他杂项

本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知淘文阁网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

工信部备案号:黑ICP备15003705号© 2020-2023 www.taowenge.com 淘文阁