绅士亦花心

本博客主题为软件开发。转载使用请遵循署名非商业原则。

用户操作
[即时聊天] [发私信] [加为好友]
姓名ID:ken0426
20178次访问,排名5913(1)好友0人,关注者2
一个好人
ken0426的文章
原创 23 篇
翻译 0 篇
转载 0 篇
评论 36 篇
ken0426的公告

Welcome!

You guys can just call me Ken, :)

Email/Gtalk:
ken0426(#)gmail.com

多多指教。谢谢。

我为什么叫绅士亦花心?
有必要解释一下



最近评论
DarkBlue:点击右键的时候按钮上的虚线还在?
流水线:东大产品主要有:摩托车总装流水线、成套悬挂物料输送线、摩托车发动机生产线、部件装配线、包装流水线;洗衣机、冰箱、空调等家电生产流水线设备.
流水线:东大产品主要有:摩托车总装流水线、成套悬挂物料输送线、摩托车发动机生产线、部件装配线、包装流水线;洗衣机、冰箱、空调等家电生产流水线设备.
流水线:东大产品主要有:摩托车总装流水线、成套悬挂物料输送线、摩托车发动机生产线、部件装配线、包装流水线;洗衣机、冰箱、空调等家电生产流水线设备.
流水线:东大产品主要有:摩托车总装流水线、成套悬挂物料输送线、摩托车发动机生产线、部件装配线、包装流水线;洗衣机、冰箱、空调等家电生产流水线设备.
文章分类
收藏
相册
1.饼子堂
2.文章X图
WinX官网
WinX Blog
WinX Forum
WinX Home
WinX团队
*加入我们?
ebasil
renfengxing
绅士亦花心
许伟群
许式伟(WinX之父)
饼子博客
Dot99
Ricky
txj_killer
废人
影子
晨星
狗狗
猪头
秃子
老迈
参考网站
codeproject
slashdot
sourceforge
友情链接
Kchen
leeson
Victor
存档
软件项目交易
订阅我的博客
XML聚合  FeedSky
订阅到鲜果
订阅到Google
订阅到抓虾
订阅到BlogLines
订阅到Yahoo
订阅到GouGou
订阅到飞鸽
订阅到Rojo
订阅到newsgator
订阅到netvibes

原创 如何编写线程安全的DLL收藏

新一篇: WinX教程之我的实战(八) | 旧一篇: WinX教程之我的实战(七)

在我的工作中经常会编写DLL,这些DLL有个共同的特点就是都有一个初始化函数,一个资源释放函数,其他几个函数都是核心功能函数。而且这些DLL有时会被多个进程同时调用,这就牵扯到多进程的多线程调用DLL的问题。有点绕口,以下我根据我实践中遇到的问题,分四种情况分享一下我解决此类问题的经验:

1、动态库只有一个导出函数。

这种情况非常少,也是最容易处理的情况。这种情况下编写函数时,只需要考虑不要有冲突的全局数据就可以了。这里的全局数据包括了在堆中分配的数据块和静态全局变量等。如果存在这样的全局数据,那么进程中的不同线程访问这个函数就会造成冲突。

解决办法也很简单,就是尽量用堆栈(stack)来解决问题。由于堆栈的所有人是线程,所以它必然是线程安全的。当然也要注意避免堆栈溢出。

我们都知道,如果要在函数再次调用时保留前一次调用的状态,可以使用静态变量。但如果你要保持函数的线程安全,那么静态变量是不能用的,因为静态变量是全局的,是属于进程的,也就是属于进程内线程共享的。所以如果确实需要在同一线程中保持函数的状态,相当于在不同次调用间传递参数,可以考虑使用静态全局线程局部变量,即:
 __declspec( thread ) int tls_i = 1;
 该变量定义就使编译器保证了tls_i是对应于每个线程的,即每个线程都一个tls_i的副本(copy),这样必然就是线程安全的。

2、动态库导出了多个函数,而且多个函数间存在数据传递。

就像前面说的,一般DLL都导出多个函数,一个初始化,一个资源释放,其他为核心功能函数。这些函数间极有可能发生数据传递。如果一个初始化函数是在线程A中调用的,而核心功能函数是在线程B中调用的,那么线程A初始化函数的资源就无法对应线程B中的核心功能,此外还有核心功能函数间的数据传递,这样的DLL就不是线程安全的,必然导致错误。

解决办法是由用户(即使用DLL的人)保证这些导出函数是在一个线程中调用。但这样会很大程度上限制接口的设计和用户的使用自由度。所以最好的方法是函数只管自己的线程安全,不同函数传递数据用动态TLS,线程局部存储。

比如:
我在全局定义了一个变量,用于存储当前线程局部存储的index ID。
__declspec( thread ) int tls_i = 1;
当调用分配资源的函数时,调用动态TLS函数TlsAlloc,分配一个ID,将其记录在全局的线程安全的tls_i变量,并通过TlsSetValue函数将数据保存在线程安全的区域;当调用获取资源的函数时,通过TlsGetValue获取资源,处理完成后,调用Tlsfree对TLS index释放,以便新线程占有。

这样,只要DLL中每个函数保证其局部是线程安全的,函数间传递数据通过TLS(静态和动态),就可以实现整个DLL的线程安全。

3、限制访问DLL中某一函数的线程数目。

有时候,对于DLL中的某一个函数的访问线程数目是有限制的,超过了限制其他线程就得等一定的时间,一定的时间过后如果还不能得到执行机会,那就返回超时。这样的设计对用户来说是友好的,而且很实用,有的商业程序确实是按照允许用户访问的通道数目来计价的。

对DLL中的函数做这样的一个封装,一般是简单的待用Semaphore信号量,来解决。DLL初始化时调用CreateSemaphore函数对信号量进行初始化,其原型如下:
HANDLE CreateSemaphore(
  LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
                       // pointer to security attributes
  LONG lInitialCount,  // initial count
  LONG lMaximumCount,  // maximum count
  LPCTSTR lpName       // pointer to semaphore-object name
);
对于信号量,它每WaitForSingleObject一次(当然是要进入),其状态值(一个整数)就减1,使用完ReleaseSemaphore其状态值就加1,当其状态值为0时信号量就由有信号变为无信号。利用信号量的这一特性,我们在初始化时将信号量的初始值(第2个参数)设置为限制的线程访问数目。在要限制访问线程数目的函数内部,通过调用WaitForSingleOject获取控制权,并指定一个等待时间(这个由配置文件指定),根据情况超时返回,使用完ReleaseSemaphore释放对占用,让其他线程可以调用这个函数。

4、多进程情况下的多线程安全DLL。

前面3讲了有时候需要对某一函数的访问线程进行限制,而我们知道,DLL是可以被多个进行加载并调用的。那就是说如果我们只对一个进程进行了限制,那么在多进程调用的情况下,这样的限制被轻易攻破。

我们都知道,Semaphore信号量属于内核对象,也就是说其可以被多进程共享访问,也就说,如果我们给一个Semaphore指定了一个名字,在另一个进程中,我们只要调用OpenSemaphore函数用同一名字打开信号量就可以访问了。这样问题就解决了?

现实情况是,多进程情况下,一般不是简单的多进程共享一个Semaphore就可以了。多进程间需要互通很多信息。一般的解决办法是,采用共享数据段。
#pragma data_seg("share")
int share_data;
#pragma data_seg()
#pragma comment(linker,"/SECTION:share, RWS")
通过pragam编译器指令生成了一个名叫share的共享数据段,这样对于变量share_data就可以多进程共享的了。如果要多进程间交换数据,只要在data_seg中添加数据定义即可。

在实践过程中还会有一些细节问题,但基本解决方案就在这里,只要把以上四种情况融会贯通、活学活用,相信一般的线程安全DLL的问题是不会难倒你了,反正我是没被难倒!:) 

发表于 @ 2007年03月23日 17:29:00|评论(loading...)|编辑

新一篇: WinX教程之我的实战(八) | 旧一篇: WinX教程之我的实战(七)

评论

#green 发表于2007-10-23 14:25:30  IP: 219.140.61.*
写的不错,只是还是有些难度的,能不能给个例程学习一下呢???
#yazhou 发表于2008-04-21 09:03:52  IP: 219.150.138.*
af
#流水线 发表于2008-09-02 10:45:41  IP: 218.75.9.*
东大产品主要有:摩托车总装流水线、成套悬挂物料输送线、摩托车发动机生产线、部件装配线、包装流水线;洗衣机、冰箱、空调等家电生产流水线设备.
发表评论  


登录
Csdn Blog version 3.1a
Copyright © ken0426