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中添加数据定义即可。

c++如何编写线程安全的DLL的更多相关文章

  1. 使用远程线程来注入DLL

    使用远程线程来注入DLL DLL注入技术要求我们目标进程中的一个线程调用LoadLibrary来载入我们想要的DLL (1)用OpenProcess函数打开目标进程(2)用VirtualAllocEx ...

  2. 《windows核心编程系列》十九谈谈使用远程线程来注入DLL。

    windows内的各个进程有各自的地址空间.它们相互独立互不干扰保证了系统的安全性.但是windows也为调试器或是其他工具设计了一些函数,这些函数可以让一个进程对另一个进程进行操作.虽然他们是为调试 ...

  3. C++编写一个简单的DLL

    什么是DLL: 自从微软推出16位的Windows操作系统起,此后每种版本的Windows操作系统都非常依赖于动态链接库(DLL)中的函数和数据,实际上 Windows操作系统中几乎所有的内容都由DL ...

  4. c++封装编写线程池

    在csapp学习或者其他linux底层编程的过程中,一般都会举一些多线程或多进程的例子,配合底层同步原语.系统调用api来解释怎么创建多线程/多进程. 但是这些例子和实际项目中所用到的多线程/多进程编 ...

  5. 实战DELPHI:远程线程插入(DLL注入)

    http://www.jx19.com/xxzl/Delphi/2010/04/17/ShiZhanDELPHI_YuanChengXianChengChaRu_DLLZhuRu/ 远程注入DLL方法 ...

  6. C++编写动态库(.DLL)给C#调用方法

    1.在头文件中按照如下格式编写函数申明 extern "C" __declspec(dllexport) double __stdcall Add(double a, double ...

  7. JavaScript 编写线程代码引用Concurrent.Thread.js

    马上来下载和使用源码吧!假定你已经将下载的源码保存到一个名为Concurrent.Thread.js的文件夹里,在进行任何操作之前,先运行如下程序,这是一个很简单的功能实现: <script t ...

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

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

  9. Java中编写线程安全代码的原理(Java concurrent in practice的快速要点)

    Java concurrent in practice是一本好书,不过太繁冗.本文主要简述第一部分的内容. 多线程 优势 与单线程相比,可以利用多核的能力; 可以方便的建模成一个线程处理一种任务; 与 ...

随机推荐

  1. CentOS 7(64位) 下Docker的安装

    系统要求是64位,内核版本至少3.10. 首先添加yum软件源: 之后更新yum软件源缓存,并安装docker-engine 查看docker 版本: Cannot connect to the Do ...

  2. 调整JVM堆内存解决OutOfMemoryError

    今天在用 processing(http://zh.wikipedia.org/wiki/Processing) 编写处理 midi 文件的程序的时候,遇到了一个问题.程序主要是读取分析 midi , ...

  3. JavaScript -- 广告随鼠标移动, 点击一次后关闭

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  4. 谷歌地图OGC WMTS服务规则

    http://mt0.google.cn/vt/lyrs=s&x=0&y=0&z=1 其中:z即为瓦片的层次,0层覆盖全球:y为行,从上往下为0~2^z-1:x为列,从左往右依 ...

  5. The type java.io.ObjectInputStream cannot be resolved. It is indirectly referenced from required .class files

    要解决的话,方法有两个 1)可以选用较低版本的sdk,比如我就用回1.6版本的sdk window->preferences->Java->Installed JREs->Ad ...

  6. 在win+r中常用的命令

    cmd打开命令提示符 regedit打开注册表 gpedit.msc组策略 services.msc打开服务列表 msconfig系统配置(可以设置开机自启动) compmgmt.msc 计算机管理 ...

  7. 19条ANDROID平台设计规范平台设计规范

    1.尺寸以及分辨率: Android的界面尺寸比较流行的有:480*800.720*1280.1080*1920,我们在做设计图的 时候建议是以 480*800的尺寸为标准: 2.界面基本组成元素: ...

  8. 机器学习(四)—逻辑回归LR

    逻辑回归常见问题:https://www.cnblogs.com/ModifyRong/p/7739955.html 推导在笔记上,现在摘取部分要点如下: (0) LR回归是在线性回归模型的基础上,使 ...

  9. poj 1300 欧拉图

    http://poj.org/problem?id=1300 要不是书上有翻译我估计要卡死,,,首先这是一个连通图,鬼知道是那句话表示出来的,终点必须是0,统计一下每个点的度数,如果是欧拉回路那么起点 ...

  10. ElasticSearch聚合分析API——非常详细,如果要全面了解的话,最好看这个

    转自:http://www.tianyiqingci.com/2016/04/11/esaggsapi/ 前言 说完了ES的索引与检索,接着再介绍一个ES高级功能API – 聚合(Aggregatio ...