加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

POCO SocketReactor框架

(2017-02-15 23:59:05)

今天分析了下POCO SocketReactor类,是一种Reactor反应器模式的使用。主要有两点十分值得了解:
1. Reactor模式的实现方法。
http://s3/mw690/001kFZtMzy78OnJpS4Ga2&690SocketReactor框架" TITLE="POCO SocketReactor框架" />

2. POCO ThreadImp类中对TLS (Thread Local storage)的使用。

class Foundation_API ThreadImpl
{
private:
class CurrentThreadHolder
{
public:
CurrentThreadHolder(): _slot(TlsAlloc())
{
if (_slot == TLS_OUT_OF_INDEXES)
throw SystemException("cannot allocate thread context key");
}
~CurrentThreadHolder()
{
TlsFree(_slot);
}
ThreadImpl* get() const
{
return reinterpret_cast(TlsGetValue(_slot));
}
void set(ThreadImpl* pThread)
{
TlsSetValue(_slot, pThread);
}
private:
DWORD _slot;
};

static CurrentThreadHolder _currentThreadHolder;
};

可以看到ThreadImpl类中定义了一个CurrentThreadHolder 静态成员,用于存储实际线程的HANDLE _thread,用可以通过静态成员函数ThreadImpl* ThreadImpl::currentImpl()随时访问。 

开始看到这里会非常疑惑,用类的静态成员变量存储线程的HANDLE _thread,那正常一个类只会共享一个_currentThreadHolder实例,多个线程的HANDLE根本无法使用,会错误获取非本线程的HANDLE。
仔细看CurrentThreadHolder这一内部类将线程存在一个DWORD _slot变量中,但是非常奇怪的是用 TlsAlloc、TlsGetValue、TlsSetValue、TlsFree几个Windows函数在进行管理,其中必有玄机。
搜索后发现使用TLS机制可以让进程的各个线程拥有线程局部的静态变量,这样_slot相当于每个线程拥有自己的实例,不会彼此间错误修改。

以下是网上搜到的TLS介绍:
为什么要有TLS?原因在于,进程中的全局变量与函数内定义的静态(static)变量,是各个线程都可以访问的共享变量。在一个线程修改的内存内容,对所有线程都生效。这是一个优点也是一个缺点。说它是优点,线程的数据交换变得非常快捷。说它是缺点,一个线程死掉了,其它线程也性命不保; 多个线程访问共享数据,需要昂贵的同步开销,也容易造成同步相关的BUG。

  如果需要在一个线程内部的各个函数调用都能访问、但其它线程不能访问的变量(被称为static memory local to a thread 线程局部静态变量),就需要新的机制来实现。这就是TLS。

  线程局部存储在不同的平台有不同的实现,可移植性不太好。幸好要实现线程局部存储并不难,最简单的办法就是建立一个全局表,通过当前线程ID去查询相应的数据,因为各个线程的ID不同,查到的数据自然也不同了。

  大多数平台都提供了线程局部存储的方法,无需要我们自己去实现:

  linux:

  int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));

  int pthread_key_delete(pthread_key_t key);

  void *pthread_getspecific(pthread_key_t key);

  int pthread_setspecific(pthread_key_t key, const void *value);

  Win32

  方法一:每个线程创建时系统给它分配一个LPVOID指针的数组(叫做TLS数组),这个数组从C编程角度是隐藏着的不能直接访问,需要通过一些C API函数调用访问。首先定义一些DWORD线程全局变量或函数静态变量,准备作为各个线程访问自己的TLS数组的索引变量。一个线程使用TLS时,第一步在线程内调用TlsAlloc()函数,为一个TLS数组索引变量与这个线程的TLS数组的某个槽(slot)关联起来,例如获得一个索引变量:

  global_dwTLSindex=TLSAlloc();

  注意,此步之后,当前线程实际上访问的是这个TLS数组索引变量的线程内的拷贝版本。也就说,不同线程虽然看起来用的是同名的TLS数组索引变量,但实际上各个线程得到的可能是不同DWORD值。其意义在于,每个使用TLS的线程获得了一个DWORD类型的线程局部静态变量作为TLS数组的索引变量。C/C++原本没有直接定义线程局部静态变量的机制,所以在如此大费周折。

  第二步,为当前线程动态分配一块内存区域(使用LocalAlloc()函数调用),然后把指向这块内存区域的指针放入TLS数组相应的槽中(使用TlsValue()函数调用)。

  第三步,在当前线程的任何函数内,都可以通过TLS数组的索引变量,使用TlsGetValue()函数得到上一步的那块内存区域的指针,然后就可以进行内存区域的读写操作了。这就实现了在一个线程内部这个范围处处可访问的变量。

  最后,如果不再需要上述线程局部静态变量,要动态释放掉这块内存区域(使用LocalFree()函数),然后从TLS数组中放弃对应的槽(使用TlsFree()函数)。

 

 

TLS 是一个良好的Win32 特质,让多线程程序设计更容易一些。TLS 是一个机制,经由它,程序可以拥有全域变量,但处于「每一线程各不相同」的状态。也就是说,进程中的所有线程都可以拥有全域变量,但这些变量其实是特定对某个线程才有意义。例如,你可能有一个多线程程序,每一个线程都对不同的文件写文件(也因此它们使用不同的文件handle)。这种情况下,把每一个线程所使用的文件handle 储存在TLS 中,将会十分方便。当线程需要知道所使用的handle,它可以从TLS 获得。重点在于:线程用来取得文件handle 的那一段码在任何情况下都是相同的,而从TLS中取出的文件handle 却各不相同。非常灵巧,不是吗?有全域变数的便利,却又分属各线程。  
 

  虽然TLS 很方便,它并不是毫无限制。在Windows NT 和Windows 95 之中,有64 个DWORD slots 供每一个线程使用。这意思是一个进程最多可以有64 个「对各线程有不同意义」的DWORDs。 虽然TLS 可以存放单一数值如文件handle,更常的用途是放置指针,指向线程的私有资料。有许多情况,多线程程序需要储存一堆数据,而它们又都是与各线程相关。许多程序员对此的作法是把这些变量包装为C 结构,然后把结构指针储存在TLS 中。当新的线程诞生,程序就配置一些内存给该结构使用,并且把指针储存在为线程保留下来的TLS 中。一旦线程结束,程序代码就释放所有配置来的区块。既然每一个线程都有64 个slots 用来储存线程自己的数据,那么这些空间到底打哪儿来?在线程的学习中我们可以从结构TDB中看到,每一个thread database 都有64 个DWORDs 给TLS 使用。当你以TLS 函式设定或取出数据,事实上你真正面对的就是那64 DWORDs。好,现在我们知道了原来那些“对各线程有不同意义的全局变量”是存放在线程各自的TDB中阿。 
 

    接下来你也许会问:我怎么存取这64个DWORDS呢?我又怎么知道哪个DWORDS被占用了,哪个没有被占用呢?首先我们要理解这样一个事实:系统之所以给我们提供TLS这一功能,就是为了方便的实现“对各线程有不同意义的全局变量”这一功能;既然要达到“全局变量”的效果,那么也就是说每个线程都要用到这个变量,既然这样那么我们就不需要对每个线程的那64个DWORDS的占用情况分别标记了,因为那64个DWORDS中的某一个一旦占用,是所有线程的那个DWORD都被占用了,于是KERNEL32 使用两个DWORDs(总共64 个位)来记录哪一个slot 是可用的、哪一个slot 已经被用。这两个DWORDs 可想象成为一个64 位数组,如果某个位设立,就表示它对应的TLS slot 已被使用。这64 位TLS slot 数组存放在process database 中(在进程一节中的PDB结构中我们列出了那两个DWORDs)。 
 

下面的四个函数就是对TLS进行操作的:  

(1)TlsAlloc  

上面我们说过了KERNEL32 使用两个DWORDs(总共64 个位)来记录哪一个slot 是可用的、哪一个slot 已经被用。当你需要使用一个TLS slot 的时候,你就可以用这个函数将相应的TLS slot位置1。  

(2)TlsSetValue  

TlsSetValue 可以把数据放入先前配置到的TLS slot 中。两个参数分别是TLS slot 索引值以及欲写入的数据内容。TlsSetValue 就把你指定的数据放入64 DWORDs 所组成的数组(位于目前的thread database)的适当位置中。  

(3)TlsGetValue  

这个函数几乎是TlsSetValue 的一面镜子,最大的差异是它取出数据而非设定数据。和TlsSetValue 一样,这个函数也是先检查TLS 索引值合法与否。如果是,TlsGetValue 就使用这个索引值找到64 DWORDs 数组(位于thread database 中)的对应数据项,并将其内容传回。  

(4)TlsFree  

这个函数将TlsAlloc 和TlsSetValue 的努力全部抹消掉。TlsFree 先检验你交给它的索引值是否的确被配置过。如果是,它将对应的64 位TLS slots 位关闭。然后,为了避免那个已经不再合法的内容被使用,TlsFree 巡访进程中的每一个线程,把0 放到刚刚被释放的那个TLS slot 上头。于是呢,如果有某个TLS 索引后来又被重新配置,所有用到该索引的线程就保证会取回一个0 值,除非它们再调用TlsSetValue。

 

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有