单件(Singelton)模式可以说是众多设计模式当中,理解起来最容易,概念最为简单的一个。并且在实际的设计当中也是使用得又最为频繁 的,甚至有很多其它的模式都要借助单件才能更好地实现。然而就是这样被强烈需求的“一句话模式”(一句话就能阐述明白),虽然有无数的牛人浸淫其中,至今 也没有谁鼓捣出一个完美的实现。我小菜鸟一只自然更不敢逢人便谈单件。不过这个贴的主题是跟单件模式是密不可分的。
什么又叫做“线程相关的单件模式”呢?也许你已经顾名思义猜出了八九分。不过还是允许我简单地用实例说明一下。
假设你要设计了一个简单的 GUI 框架,这个框架当中需要这样一个全局变量(单件模式),它保存了所有窗口句柄与窗口指针的映射(我见过的数个的开源 GUI 框架都有类似的东西。)。在 WIN32 平台上就是这样一个简单的东西:
//窗口的包装类
class Window
{
HWND m_hwnd;
public:
bool create();
bool destroy();
//其它细节
};
//窗口句柄与其对象指针的映射
typedef map<HWND,Window*> WindowMap;
typedef WindowMap::iterator WindowIter;
WindowMap theWindowMap;
每创建一个窗口,就需要往这个 theWindowMap 当中添加映射。每销毁一个窗口,则需要从其中删除掉相关映射。实现代码类似:
//创建窗口
bool Window::create()
{
m_hwnd=::CreateWindow(/*参数略*/);
if(!::IsWindow(m_hwnd))
return false;
theWindowMap[m_hwnd]=this; //添加映射
return true;
}
//销毁窗口
bool Window::destroy()
{
::DestroyWindow(m_hwnd);
theWindowMap.erase(m_hwnd); //删除映射
return true;
}
你可以用任何可能的单件模式来实现这样一个全局变量 theWindowMap,它会 工作得很好。但是当如果考虑要给程序添加多线程支持(“多线程”是如此麻烦,它总爱和“但是”一起出现,给本来进行得很顺利的事情引起波折。),就会发现 此时也许纯粹的单件模式并不是最好的选择。例如一个线程同时创建窗口,那么两个线程同时调用:
theWindowMap[m_hwnd]=this;
这显然不是一个原子操作,可以肯定如果你坚持这样干你的程序会慢慢走向崩溃,幸运一点只是程序运行结果错误,如果你恰好那几天印堂发暗面色发灰,说不定就因为这小小的错误,被无良的BOSS作为借口开除掉了,那可是个悲惨的结局。
当然大多数的单件模式已经考虑到了多线程的问题。它们的解决方案就是给加上线程锁 ,我在数个开源的 GUI 框架看到他们都采用了这种解决方案。不过这样做,在线程同步过程当中,会产生与 GUI 框架逻辑不相关的同步消耗,虽然不是什么大不了的消耗,但是客户可能因此就选择了你的竟争对手,如果线程竟争激烈,在强烈渴求资源的环境(如小型移动设 置)当中,这种消耗更是不可忽视的。
实际上在应用当中,极少有线程需要插入删除其它线程创建的窗口映射(如果确实有这种需要,那么可以肯定项目的设计上出了问题)。在这种情况下本 线程创建窗口映射都将只是本线程存取,类似“Thread-Specific”的概念。也就是说,theWindowMap 当中其它线程创建的窗口的映射对于本线程来说都是不需关心的,我们却要为那部分不必要东西整天提心吊胆并付出运行时消耗的代价,这也有点像“穿着棉袄洗 澡”。但是怎么样才能做到更轻松爽快些呢?
就本例问题而言,我们需要这样一种变量来保存窗口映射,它针对每个线程有不同的值(Thread-Specific Data),这些值互不影响,并且所有线程对它的访问如同是在访问一个进程内的全局变量(Singelton)。
如果你是熟悉多线程编程的人,那么“Thread-Specific ”一定让你想起了什么。是的,“Thread-Specific Storage ” (线程相关存存诸,简称 TSS ),正是我们需要的,这是大多数操作系统都提供了的一种线程公共资源安全机制,这种机制允许以一定方式创建一个变量,这个变量在所在进程当中的每个线程当 中,可以拥有不同的值。在 WIN32 上,这个变量就称为“索引”,其相关的值则称为“槽”, “Thread-Local Storage”(线程局部存诸,简称 TLS )机制。它的提了供这样几个函数来定义,设置,读取线程相关数据(关于 TLS 的更多信息,可以查阅 MSDN ):
//申请一个“槽”的索引。
DWORD TlsAlloc( void );
//获得调用线程当中指定“槽”的值。
VOID* TlsGetValue( DWORD dwTlsIndex );
//设置调用线程当中指定“槽”的值。
BOOL TlsSetValue( DWORD dwTlsIndex,VOID* lpTlsValue );
//释放掉申请的“槽”的索引
BOOL TlsFree( DWORD dwTlsIndex );
具体使用流程方法:先调用 TlsAlloc 申请一个“索引”,然后线程在适当时机创建一个对象并调用 TlsSetValue 将“索引”对应的“槽”设置为该对象的指针,在此之后即可用 TlsGetValue 访问该“糟”。最后在不需要的时候调用 TlsFree ,如在本例当中,调用 TlsFree 的最佳时机是在进程结束时。
先封装一下 TlsAlloc 和 TlsFree 以方便对 ”索引“的管理。
class TlsIndex
{
public:
TlsIndex()
:m_index(::TlsAlloc())
{}
~TlsIndex()
{
::TlsFree(m_index);
}
public:
operator DWORD() const
{
return m_index;
}
private:
DWORD m_index;
};
如你所见,类 TlsIndex 将在构造的时候申请一个“索引”,在析构的时候释放此“索引”。
在本例当中 TlsIndex 的对象应该存在进程的生命周内,以保证在进程退出之前,这个“索引”都不会被释放,这样的 TlsIndex 对象听起来正像一个全局静态对象,不过 Meyers Singelton (用函数内的静态对象实现)在这里会更适合,因为我们不需要对这个对象的生命周末进行精确控制,只需要它在需要的时候创建,然后在进程结束前销毁即可。这 种方式只需要很少的代码即可实现,比如:
DWORD windowMapTlsIndex()
{
static TlsIndex s_ti; //提供自动管理生命周期的“索引”
return s_ti;
}
利用这个“索引”,我们就能实现上述“Thread-Specific”的功能:
WindowMap* windowMap()
{
WindowMap* wp=reinterpret_cast<WindowMap*>(::TlsGetValue(windowMapTlsIndex()));
if(!wp)
{
wp=new WindowMap();
::TlsSetValue(windowMapTlsIndex(),wp);
}
return wp;
}
#define theWindowMap *(windowMap())
注意各线程访问以上的代码不会存在竟争。这样就实现了一个线程安全且无线程同步消耗版本的“全局对象” theWindowMap 。我们甚至不用改变Window::create,Window::destory,queryWindow 的代码,
这几个简单的函数看起来似乎不像一个“模式”,但是它确实是的。
现在总结一下“线程相关的单件模式”的概念:保证一个类在一个线程当中只有一个实例,并提供一个访问它的线程内的访问点的模式。
为了不重复地制造车轮,我将此类应用的模式封装了一下:
template<typename TDerived>
class TlsSingelton
{
typedef TDerived _Derived;
typedef TlsSingelton<TDerived> _Base;
public:
static _Derived* tlsInstance()
{
return tlsCreate();
}
protected:
static _Derived* tlsCreate()
{
_Derived* derived=tlsGet();
if(derived)
return derived;
derived=new _Derived();
if(derived && TRUE==::TlsSetValue(tlsIndex(),derived))
return derived;
if(derived)
delete derived;
return NULL;
}
static bool tlsDestroy()
{
_Derived* derived=tlsGet();
if(!derived)
return false;
delete derived;
return true;
}
static DWORD tlsIndex()
{
static TlsIndex s_tlsIndex;
return s_tlsIndex;
}
private:
static _Derived* tlsGet()
{
return reinterpret_cast<_Derived*>(::TlsGetValue(tlsIndex()));
}
static bool tlsSet(_Derived* derived)
{
return TRUE==::TlsSetValue(tlsIndex(),derived);
}
//noncopyable
private:
TlsSingelton(const _Base&);
TlsSingelton& operator=(const _Base&);
};
将 tlsCreate,tlsDestroy 两个函数设置为保护成员,是为了防止一些不三不四吊尔啷噹的程序随意地删除。
示例:
class WindowMapImpl:public TlsSingelton<WindowMap>
{
WindowMap m_map;
public:
WidnowMap& theWindowMapImpl()
{
return m_map;
}
public:
~WindowMapImpl();
protected:
WindowMapImpl(); //只能通过tlsCreate创建
friend class _Base;
};
#define theWindowMap (WindowMapImpl::tlsInstance()->theWindowMapImpl())
仍不需要修改原有窗口代码。