c++如何编写线程安全的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=0;//需要初始化,否则全局变量被放到BSS段中,从而导致共享失败
#pragma data_seg()
#pragma comment(linker,"/SECTION:share, RWS") //如果不加这句,只是单纯的说明他们是放到数据段中,还没有共享
通过pragam编译器指令生成了一个名叫share的共享数据段,这样对于变量share_data就可以多进程共享的了。如果要多进程间交换数据,只要在data_seg中添加数据定义即可。
c++如何编写线程安全的DLL的更多相关文章
- 使用远程线程来注入DLL
使用远程线程来注入DLL DLL注入技术要求我们目标进程中的一个线程调用LoadLibrary来载入我们想要的DLL (1)用OpenProcess函数打开目标进程(2)用VirtualAllocEx ...
- 《windows核心编程系列》十九谈谈使用远程线程来注入DLL。
windows内的各个进程有各自的地址空间.它们相互独立互不干扰保证了系统的安全性.但是windows也为调试器或是其他工具设计了一些函数,这些函数可以让一个进程对另一个进程进行操作.虽然他们是为调试 ...
- C++编写一个简单的DLL
什么是DLL: 自从微软推出16位的Windows操作系统起,此后每种版本的Windows操作系统都非常依赖于动态链接库(DLL)中的函数和数据,实际上 Windows操作系统中几乎所有的内容都由DL ...
- c++封装编写线程池
在csapp学习或者其他linux底层编程的过程中,一般都会举一些多线程或多进程的例子,配合底层同步原语.系统调用api来解释怎么创建多线程/多进程. 但是这些例子和实际项目中所用到的多线程/多进程编 ...
- 实战DELPHI:远程线程插入(DLL注入)
http://www.jx19.com/xxzl/Delphi/2010/04/17/ShiZhanDELPHI_YuanChengXianChengChaRu_DLLZhuRu/ 远程注入DLL方法 ...
- C++编写动态库(.DLL)给C#调用方法
1.在头文件中按照如下格式编写函数申明 extern "C" __declspec(dllexport) double __stdcall Add(double a, double ...
- JavaScript 编写线程代码引用Concurrent.Thread.js
马上来下载和使用源码吧!假定你已经将下载的源码保存到一个名为Concurrent.Thread.js的文件夹里,在进行任何操作之前,先运行如下程序,这是一个很简单的功能实现: <script t ...
- 编写线程安全的Java缓存读写机制 (原创)
一种习以为常的缓存写法: IF value in cached THEN return value from cache ELSE compute value save value in cache ...
- Java中编写线程安全代码的原理(Java concurrent in practice的快速要点)
Java concurrent in practice是一本好书,不过太繁冗.本文主要简述第一部分的内容. 多线程 优势 与单线程相比,可以利用多核的能力; 可以方便的建模成一个线程处理一种任务; 与 ...
随机推荐
- Datax官方笔记总结
# DataX DataX 是阿里巴巴集团内被广泛使用的离线数据同步工具/平台,实现包括 MySQL.SQL Server.Oracle.PostgreSQL.HDFS.Hive.HBase.OTS. ...
- mysql基础(3)-高级查询
聚合函数 count 返回查询结果的条数 max 返回查询结果的最大值 min 返回查询结果的最小值 sum 返回查询结果的和 avg 返回查询结果的平均值 统计分数大于等于90的人数: mysq ...
- HDU 5884 Sort(2016年青岛网络赛 G 二分+贪心+小优化)
好题 题意:给你n<=100000个数,每个数范围[0,1000],然后给你一个最大的代价T,每次最多合并k个数成为一个数,代价为k个数的总和.问最后合成1个数的总代价不大于T的最小k 题解:我 ...
- Spring的DI初步
DI:依赖注入 第一个DEMO:域属性注入 java类:(Car类和Stu类,学生有一辆小汽车) package cn.dawn.day02di; /** * Created by Dawn on 2 ...
- dataframe to sql
使用pandas的to_sql时,还涉及到一些sql数据库的操作 在网上找到一段代码,很方便使用.来源:blog.csdn.net/qcyfred/article/details/78085243?l ...
- 写hibernate.cfg.xml时报错The content of element type "property" must match "(meta*,(column|formula)*,type?)".
原配置文件是这样的 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-ma ...
- java集合转换成json时问题和解决方法
json+hibernate死循环问题的一点见解,有需要的朋友可以参考下. [问题]如题所示,在我们使用hibernate框架而又需要将对象转化为json的时候,如果配置了双向的关联关系,就会出现这个 ...
- 使用 ActiveMQ 创建 Java 应用
本章重点 Java 应用中内嵌 ActiveMQ 使用 Spring 内嵌 ActiveMQ 创建请求/响应应用 使用 Spring 构建 JMS 客户端
- js:for循环ul/li,获取当前被点击元素的id,以及给其他li设置属性
js:for循环ul/li,获取当前被点击元素的id,以及给其他li设置属性 <!doctype html> <html> <head> <meta char ...
- WebAPI Post请求多参数处理方案
contentType:"application/json"You need to use JSON.stringify method to convert it to JSON ...