在多线程编程中,时刻需要注意加锁,这也是多线程编程中的一个难点。
但是我们所用到的锁都是“协议锁”,即是一个“君子协定”,所谓“防君子不防小人”。这也是多线程编程中比较让人肾疼的地方。
我觉得用“锁”这个名词对编程中的这种操作现象进行描述其实是不太合适的,反而会引起歧义,把它看成是门口挂的一块“告示牌”会更合适一些 ——— 一面写着“有人勿进”,一面写着“空闲可用”。
你的线程作为一个“君子”,在到达访问临界资源的门(门上是没有锁的,也没办法给它装个锁)前,首先要看看这块告示牌,如果写着的是“有人勿进”,那么你就在门口等着,时不时再瞄一眼,看告示牌是不是翻了个面了,也可能是在你等得快睡着的时候被人叫醒。
否则你就可以进去,顺便把优雅的将告示牌翻个面。
等你从门里出来的时候,或许你可以看见门口也等了一大堆像你一样的君子了,那么就看你是默默翻转告示牌离开,还是和他们打声招呼了。
如果所有的人都像你这么君子作风,那么应该没什么问题了,井然有序,虽然可能办事效率会很低啦。然而总保不准会有一两个非君子的小人,进门不看告示牌,不管三七二十一,冲进去拿了东西就走,还不顺手把门口的告示牌给翻一下。
当然如果他进门的时候刚好是“空闲可用”状态,或者即便是有人,但他拿的东西也不是别人想要的,那么顶多也就是虚惊一场。怕就怕的是起了冲突,那么很不幸,你的软件可能就要崩溃了。。
下面说说我遇到的比较肾疼的问题:
接手的项目实在是惨不忍睹。有太多诸如定义了一个单例类对象,必然是需要当多个线程访问的临界资源的,然而却提供着 static MyCls& MyCls::GetMyCls();这种借口。然而本来应该提供的需要在内部加锁的“增删改查”接口可能却压根没有!!!
如果只是一个类这样写还好,自己重构一下问题不大,然而当整个项目几乎都这样的时候,我也无能为力。
面对偶发并且频率极高的崩溃,也只能死马当活马医,加锁吧。。。
需要在定义这个类的模块中增加锁成员,并且导出出去供其他模块使用,多么肾疼的写法。。。
好吧,只好用C语言强大万能的宏来包装一下,搞一个返回值的锁吧。
代码如下:
1. 封装锁:
//CriticalSection_Lock.h
#pragma once
#include <windows.h>
class CriticalSection_Lock
{
public:
CriticalSection_Lock(void);
~CriticalSection_Lock(void);
//{重载new delete解决跨模块的析构
void *operator new(size_t size){return ::operator new(size);}
void *operator new[](size_t size){return ::operator new(size);}
void operator delete(void *p){return ::operator delete(p);}
void operator delete[](void *p){return ::operator delete(p);}
//}}
private:
static CRITICAL_SECTION *m_cs;
};
#define CS_LOCK(name) { CriticalSection_Lock lock_##name;
#define CS_UNLOCK(name) }
//CriticalSection_Lock.cpp
#include "CriticalSection_Lock.h"
CRITICAL_SECTION *CriticalSection_Lock::m_cs = nullptr;
CriticalSection_Lock::CriticalSection_Lock(void)
{
if (nullptr == m_cs)
{
static CRITICAL_SECTION cs;//实际的代码不应该这样写,也不是这么写的,这里只是给个简单的例子,用了一个全局的锁。不过即便像这样的示例代码,也是有问题的,需要使用双检锁避免创建多个cs实例(如果真的需要使用全局单例的话)
m_cs = &cs;
InitializeCriticalSectionEx(m_cs,4000,0);
}
EnterCriticalSection(m_cs);
}
CriticalSection_Lock::~CriticalSection_Lock(void)
{
LeaveCriticalSection(m_cs);
}
2.返回值锁相关的宏:
//RetValueLock.h
#pragma once
#define RETURN_NEED_LOCK
#ifdef RETURN_NEED_LOCK
#define RV_WITH_LOCK_DECL(Type) \
struct RetWithLock##Type \
{ \
RetWithLock##Type(Type type, std::shared_ptr<CriticalSection_Lock> sp) \
:m_type(type) \
,m_sp(sp){} \
Type m_type; \
std::shared_ptr<CriticalSection_Lock> m_sp; \
};
#define RV_WITH_LOCK(Type) RetWithLock##Type
#define RETURN_RET_WITH_LOCK(Type,name) \
CriticalSection_Lock *pcs = new CriticalSection_Lock; \
std::shared_ptr<CriticalSection_Lock> spint(pcs); \
return RetWithLock##Type(name,spint);
#define GET_REAL_RET_FROM_LOCKRET(Name) Name.m_type
#else
#define RV_WITH_LOCK_DECL(Type)
#define RV_WITH_LOCK(Type) Type
#define RETURN_RET_WITH_LOCK(Type,name) return name;
#define GET_REAL_RET_FROM_LOCKRET(Name) Name
#endif
3.使用:
//1.typedef成一个可以放进标识符的名字
typedef MyCls& MyClsRef;
//2.替换GetMyCls();等申明和实现
//MyCls& MyCls::GetMyCls()
RV_WITH_LOCK(MyClsRef) MyCls::GetMyCls()
{
//....
}
//3.替换外部调用GetMyCls()的地方
///MyCls& v = MyCls::GetMyCls();
RV_WITH_LOCK(MyClsRef) lv = GetVct();
MyClsRef v = GET_REAL_RET_FROM_LOCKRET(lv);
然后基本上几个简单的正则替换就搞定了,崩溃是没有了,然而至于性能,呵呵,管他呢,毕竟我只是个擦屁股的,我还能奢望我自己做得有多好呢[笑哭脸]。。。