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=0;//需要初始化,否则全局变量被放到BSS段中,从而导致共享失败
#pragma data_seg()
#pragma comment(linker,"/SECTION:share, RWS") //如果不加这句,只是单纯的说明他们是放到数据段中,还没有共享
通过pragam编译器指令生成了一个名叫share的共享数据段,这样对于变量share_data就可以多进程共享的了。如果要多进程间交换数据,只要在data_seg中添加数据定义即可。

转载于:https://www.cnblogs.com/duyy/p/3741829.html

c++如何编写线程安全的DLL相关推荐

  1. 写缓存java,编写线程安全的Java缓存读写机制 (原创)

    一种习以为常的缓存写法: IF value in cached THEN return value from cache ELSE compute value save value in cache ...

  2. C/C++:Windows编程—创建进程、终止进程、枚举进程、枚举线程、枚举DLL

    创建进程的2种方式 1. 创建进程最简单的方法 UINT WINAPI WinExec(_In_ LPCSTR lpCmdLine, // 指向可执行文件_In_ UINT uCmdShow // 程 ...

  3. Windows核心编程_远线程方式实现Dll注入

    之前有介绍过HOOK的方式注入,这次介绍以其它方式注入,而无须HOOK,要知道在Windows这个浩荡的海洋里,API就是宝藏,找到足够多的宝藏那么你就是海贼王~! 实现思路如下: 首先打开一个进程的 ...

  4. (二) 使用Detours调试远程线程注入的dll

    远程线程注入是指一个进程在另一个进程中创建线程的技术.该技术可以用于:API Hook,破解软件所谓的"内存补丁"等. 将DLL注入到其它进程并不是难事,问题是这个被注入的DLL不 ...

  5. 编写线程安全的Java缓存读写机制 (原创)

    一种习以为常的缓存写法: IF value in cached THENreturn value from cache ELSEcompute valuesave value in cacheretu ...

  6. php 编写线程教程,php 实现多线程

    通过php的Socket方式实现php程序的多线程.php本身是不支持多线程的,那么如何在php中实现多线程呢?可以想一下,WEB服务器本身都是支持多线程的.每一个访问者,当访问WEB页面的时候,都将 ...

  7. Java植物名录程序_程序员用Java语言编写多线程应用程序,程序员能控制的关键性工作有两个方面:一是编写线程的_________方法;二是建立线程实例。...

    沟通的目的是打造"3G团队",其中的"3G"具体指: "啊,时间过得真快啊!"中的"啊" 活塞与气缸盖.气缸壁共同组成燃 ...

  8. java 编写线程公共类_Java实现线程间通信方式

    线程间通信的模型: 共享内存 消息传递 我们来做道题理解一下 题目: 有两个线程A.B,A线程向一个集合里面依次添加元素"abc"字符串,一共添加十次,当添加到第五次的时候,希望B ...

  9. 通过编写自定义的gina.dll实现U盘开机锁

    simples.c 编译为可执行程序simples.exe 下面的程序编译为ginafuncs.dll并用它替换c:\windows\system32下的msgina.dll 开机时ginafuncs ...

最新文章

  1. linux驱动:音频驱动(六)ASoc之codec设备
  2. QFIL工具如何导出手机分区数据
  3. zTree的调用设使用(跨两个系统,两类技术实现的项目案例SpringMVC+Spring+MyBatis和Struts2+Spring+ibatis框架组合)
  4. JS ES6中export和import详解
  5. 老歌新唱--使用VB6开发的ActiveX实现.NET程序的混淆加密
  6. 关于 C语言的 按位取反 ~
  7. nsa构架_我如何使用NSA的Ghidra解决了一个简单的CrackMe挑战
  8. 判断sem信号量为零_kernel.sem信号量调优
  9. 纪念概率学界最后一位集大成者——钟开莱
  10. 【转载】project2019安装教程
  11. matlab中的对数函数,[matlab对数函数]对数函数运算法则是什么呢?
  12. Java文件拒绝访问问题
  13. win10安装navisworks失败,怎么强力卸载删除注册表并重新安装
  14. bzoj1127 [POI2008]KUP
  15. 《软件随想录-Joel on Software》书摘
  16. 多行文字显示不完用省略号表示
  17. 免费的B站短链生成器,将链接转成b23.tv
  18. 唯美计算机语言,唯美精辟的语句
  19. 南林计算机科学,南京林业大学信息科学技术学院
  20. 大佬是如何从头写一篇顶级论文的?

热门文章

  1. 我的docker随笔9:docker在centos上的安装
  2. 数组乱码_python 爬虫随笔-土办法治乱码
  3. 【Flink】 Flink 应用资源分配问题排查思路
  4. 95-910-165-源码-FlinkSQL-Flink SQL 中的时间属性
  5. 【Elasticsearch】Elasticsearch 存储桶聚合
  6. 【Flink】Flink1.12.0 FlinkSQL消费Kafka 使用 temporal join 关联维表Hive 最新分区数据 join 不上
  7. 【ElasticSearch】Es 源码之 AutoFollowCoordinator 源码解读
  8. 【ElasticSearch】Es 源码之 AliasValidator 源码解读
  9. 【kafka】JMX 监控kafka kafka rmi NoSuchObjectException no such object in table
  10. 【Kafka】 kafka 启动 Connection to node 1 could not be established. Broker may not be available