10.5.4 Tss库(2)
它的构造函数接受一个TSS键(TssKey<T>或HTssKey)和一个对外部值变量的引用,然后它通过调用Tss_GetSlotValue()将该外部变量设置为槽位值。
在析构函数中,外部变量的值跟槽位的原始值作比较,如果外部变量的值发生了改变,则通过Tss_SetSlotValue()来更新槽位值。现在我们的客户代码更简单了,依赖于RAII来对线程的槽位值进行必要的更新。
程序清单10.10
- OneThing const &func(Another *another)
- {
- OneThing *thing;
- TssSlotScope<OneThing*> scope(key_func, thing);
-
- if( . . . )
- thing = new OneThing(another);
- else if( . . . )
- thing = . . .;
- else
- . . .
-
- return *thing;
- } // scope的确定性析构确保Tls_SetSlotValue()得到调用
现在我们已经看到如何使用Tss库了,但是它的工作原理是什么呢?我把推断它的实现的任务留给你,1不过在此我们需要讨论一下线程通知是如何进行的。这涉及到我到目前为止还没提到的两个函数:Tss_ThreadAttach()和Tss_ThreadDetach()。这两个函数应该在线程开始和结束时分别被调用。如果可能的话,你可以通过将它们挂钩到操作系统或运行时库的基础设施上来达到这个目的,否则你就得手工进行相应的调用。
在Win32上,所有DLL都导出其进入点DllMain()[Rich1997],该函数在进程加载/卸载以及线程开始/结束时会收到通知消息。在Synesis Win32库中,基本DLL(MMCMNBAS.DLL)就是在其DllMain()中接收到DLL_THREAD_ATTACH通知时调用Tss_ThreadAttach(),并且在接收到Tss_ThreadDetach()时调用Tss_ThreadDetach()的。由于它是一个普通的DLL,所以任何可执行文件的其他组成部分都可以使用Tss库,而不用考虑底层的设置动作,拿来就用,很简单!
程序清单10.11
- BOOL WINAPI DllMain(HINSTANCE, DWORD reason, void *)
- {
- switch(reason)
- {
- case DLL_PROCESS_ATTACH:
- Tss_Init();
- break;
- case DLL_THREAD_ATTACH:
- Tss_ThreadAttach();
- break;
- case DLL_THREAD_DETACH:
- Tss_ThreadDetach();
- break;
- case DLL_PROCESS_DETACH:
- Tss_Uninit();
- break;
- }
- . . .
在UNIX上,该库的函数Tss_Init()中调用pthread_key_create(),创建一个私有的、未被使用过的键,其惟一的意图是确保该库在每个线程结束时都能够接收到一个回调,并借此机会调用Tss_ThreadDetach()。由于在PTHREADS中并没有支持"每线程(per-thread)"初始化函数的机制,所以当它的Tss库被请求从一个不存在的槽位中取数据时就会有"温和"的反应,并且当被请求保存一个槽位值而槽位不存在时就会新建一个。因此,Tss_ThreadAttach()可以被看作这样一种机制,它响应线程的诞生并高效地扩展所有活跃键,而并不是在线程执行的过程中逐次完成。
如果你不使用PTHREADS或Win32,或者你不希望将你的库放在一个Win32 DLL中,你就应该确保所有线程都调用了attach/detach函数。然而,即使你不能或没有这么做,当最终对Tss_Uninit()的调用到来之时,该库仍然会执行被注册的清理函数,以便清理所有键对应的所有槽位。
这是一个强大而完备的机制,惟一可能出现问题的情况就是:如果你的用于释放资源的清理函数必须在分配对应资源的线程中被调用起来,而且你又没法保证及时而无遗漏的线程attach/detach通知,你就可能会遇到麻烦。但话说回来,你干吗非得如此呢?你需要的是什么--我们只不过是原生质和硅!