多线程在使用情况下得使用原子锁InterlockedDecrement 确保他的调用次数不会被打乱
在多线程编程中,我们经常用到InterlockedDecrement , 此处有个细节问题,简单聊一下.
下面的程序用mReferenceCount来控制是否要删除自己.
void AddRef()
{}
猛的一看,好像没什么问题, 大家看Release这个函数, InterlockedDecrement是个原子操作,假设当两个线程到到达这个函数时,
mReferenceCount为2, 只有一个线程要执行InterlockedDecrement操作, 那么mReferenceCount减一为 1, 这个线程继续往下
执行,当正在比较操作时, 第二个线程也执行了InterlockedDecrement操作,那么mReferenceCount再减一为0. 好了问题出现了.
第一个线程在比较操作时, 发现mRerenceCount为0,就去执行delete操作. 当第二个线程到达比较操作时, 发现mReferenceCount
也为0, 也去执行delete操作,连续两次delete, 程序就崩溃了.
这个问题很难发现,所以写了博克, 给诸位同行提个醒.
下面是解决方案:
void Release()
{
long refCount = InterlockedDecrement(&mReferenceCount);
if (0 == refCount)
{
delete this;
}
}
我们加入了refCount局部变量,也就是说每个线程在Release函数中各自维护一个计数结果, 这样就避免了重复删除.
接口原始申明:
//定义接口 // {F1E641CD-447A-4e41-9BD8-8E49236B75A7} static const GUID IID_IBase = { 0xf1e641cd, 0x447a, 0x4e41, { 0x9b, 0xd8, 0x8e, 0x49, 0x23, 0x6b, 0x75, 0xa7 } }; //这里我们自己手写一个 其实微软已经给我们定义了一个IUnknown 里面的结构也和这个差不多 interface IBase{ public: virtual int AddRef()=0; virtual int Release()=0; virtual int QueryInterface(GUID iid,void**ppiIterface)=0; }; // {CFA60C64-D93E-48fd-9B4F-4BA53383BCAE} static const GUID IID_IMath={0xcfa60c64, 0xd93e, 0x48fd, {0x9b,0x4f,0x4b,0xa5,0x33,0x83,0xbc,0xae}}; interface IMath:IBase{ public: virtual int Add(int nAdd1,int nAdd2)=0; virtual int Sub(int nSub1,int nSub2)=0; }; // {4DF2CB94-02E2-414d-98B5-B1639C0A1371} static const GUID IID_IMath2 = { 0x4df2cb94, 0x2e2, 0x414d, { 0x98, 0xb5, 0xb1, 0x63, 0x9c, 0xa, 0x13, 0x71 } }; interface IMath2:IBase{ public: virtual int Mud(int nMud1,int nMud2)=0; virtual int Div(int nDiv1,int nDiv2)=0; }; #endif接口实现:
#include "stdafx.h" #include "math.h" BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved <span style="white-space:pre"> </span> ) { return TRUE; } class CMath:public IMath,public IMath2{ public: CMath(); virtual int AddRef(); virtual int Release(); virtual int Add(int nAdd1,int nAdd2); virtual int Sub(int nSub1,int nSub2); virtual int QueryInterface(GUID iid,void**ppiIterface); virtual int Mud(int nMud1,int nMud2); virtual int Div(int nDiv1,int nDiv2); public: LONG m_nRef; }; CMath::CMath(){ m_nRef=0; } int CMath::Add(int nAdd1,int nAdd2){ return (nAdd1+nAdd2); } int CMath::Sub(int nSub1,int nSub2){ return (nSub1-nSub2); } int CMath::AddRef(){ //用的次数加1 InterlockedDecrement(&m_nRef); return m_nRef; } int CMath::Release(){ InterlockedDecrement(&m_nRef); if(m_nRef==0){ //当用的人只有0个的时候 释放 delete this; } return m_nRef; } int CMath::Mud(int nMud1,int nMud2){ return (nMud1*nMud2); } int CMath::Div(int nDiv1,int nDiv2){ return (nDiv1/nDiv2); } //接口查询 int CMath::QueryInterface(GUID iid,void**ppiIterface){ //如果IID为第一个 就返回其指针 if(iid==IID_IMath){ *ppiIterface=static_cast<IMath*>(this); //每次判断成功指针确定之后我们就AddRef加一 AddRef(); }else if(iid==IID_IMath2){ *ppiIterface=static_cast<IMath2*>(this); AddRef(); }else if(iid==IID_IBase){ //这里不能写IBase 不然会出现混乱的转换 //因为两个类都有IBase所有他不知道应该从IMath 还是IMath2 //所以我们就给他指定第一个类 *ppiIterface=static_cast<IMath*>(this); AddRef(); } return 0; } IMath *CreateInstance(){ IMath *piMath= new CMath; //没次使用的时候 都要AddRef 我们就在这里加1 更加方便 piMath->AddRef(); return piMath; }
#include "stdafx.h" #include "../DllIntertace/math.h" typedef IMath *(*CREATEINTERACE)(); IMath *CreateInterace(){ //加载动态库 HMODULE hDll=(HMODULE)LoadLibrary("DllIntertace.dll"); //获取创建接口的函数 CREATEINTERACE CreateInstance=(CREATEINTERACE)GetProcAddress(hDll,"CreateInstance"); //创建接口 IMath *piMath=CreateInstance(); //返回接口 return piMath; } int main(int argc, char* argv[]) { //创建接口 IMath *piMath=CreateInterace(); //使用接口 int ADD=piMath->Add(100,100); <span style="white-space:pre"> </span>printf("%d\n",ADD); //接口查询 IMath2 *piMath2=NULL; //把IID_IMath2这个ID传进去识别 //如果要把IMath接口转换成IMath2接口是不可以的,组件 //不一定是C++编译的 也可能是C# JAVA都有可能 piMath->QueryInterface(IID_IMath2,(LPVOID *)&piMath2); int Muds=piMath2->Div(100,2); printf("%d\n",Muds); //释放接口 //piMath->Release(); <span style="white-space:pre"> </span>return 0; }程序下载:http://pan.baidu.com/s/1c0CcWfu