出處
http://www.shnenglu.com/cexer/archive/2008/07/08/55670.html
單件(Singelton)模式可以說是眾多設(shè)計(jì)模式當(dāng)中,理解起來最容易,概念最為簡單的一個(gè)。并且在實(shí)際的設(shè)計(jì)當(dāng)中也是使用得又最為頻繁
的,甚至有很多其它的模式都要借助單件才能更好地實(shí)現(xiàn)。然而就是這樣被強(qiáng)烈需求的“一句話模式”(一句話就能闡述明白),雖然有無數(shù)的牛人浸淫其中,至今
也沒有誰鼓搗出一個(gè)完美的實(shí)現(xiàn)。我小菜鳥一只自然更不敢逢人便談單件。不過這個(gè)貼的主題是跟單件模式是密不可分的。
什么又叫做“線程相關(guān)的單件模式”呢?也許你已經(jīng)顧名思義猜出了八九分。不過還是允許我簡單地用實(shí)例說明一下。
假設(shè)你要設(shè)計(jì)了一個(gè)簡單的 GUI 框架,這個(gè)框架當(dāng)中需要這樣一個(gè)全局變量(單件模式),它保存了所有窗口句柄與窗口指針的映射(我見過的數(shù)個(gè)的開源 GUI 框架都有類似的東西。)。在 WIN32 平臺(tái)上就是這樣一個(gè)簡單的東西:
//窗口的包裝類
class Window
{
HWND m_hwnd;
public:
bool create();
bool destroy();
//其它細(xì)節(jié)
};
//窗口句柄與其對(duì)象指針的映射
typedef map<HWND,Window*> WindowMap;
typedef WindowMap::iterator WindowIter;
WindowMap theWindowMap;
每創(chuàng)建一個(gè)窗口,就需要往這個(gè) theWindowMap 當(dāng)中添加映射。每銷毀一個(gè)窗口,則需要從其中刪除掉相關(guān)映射。實(shí)現(xiàn)代碼類似:
//創(chuàng)建窗口
bool Window::create()
{
m_hwnd=::CreateWindow(/*參數(shù)略*/);
if(!::IsWindow(m_hwnd))
return false;
theWindowMap[m_hwnd]=this; //添加映射
return true;
}
//銷毀窗口
bool Window::destroy()
{
::DestroyWindow(m_hwnd);
theWindowMap.erase(m_hwnd); //刪除映射
return true;
}
你可以用任何可能的單件模式來實(shí)現(xiàn)這樣一個(gè)全局變量 theWindowMap,它會(huì)
工作得很好。但是當(dāng)如果考慮要給程序添加多線程支持(“多線程”是如此麻煩,它總愛和“但是”一起出現(xiàn),給本來進(jìn)行得很順利的事情引起波折。),就會(huì)發(fā)現(xiàn)
此時(shí)也許純粹的單件模式并不是最好的選擇。例如一個(gè)線程同時(shí)創(chuàng)建窗口,那么兩個(gè)線程同時(shí)調(diào)用:
theWindowMap[m_hwnd]=this;
這顯然不是一個(gè)原子操作,可以肯定如果你堅(jiān)持這樣干你的程序會(huì)慢慢走向崩潰,幸運(yùn)一點(diǎn)只是程序運(yùn)行結(jié)果錯(cuò)誤,如果你恰好那幾天印堂發(fā)暗面色發(fā)灰,說不定就因?yàn)檫@小小的錯(cuò)誤,被無良的BOSS作為借口開除掉了,那可是個(gè)悲慘的結(jié)局。
當(dāng)然大多數(shù)的單件模式已經(jīng)考慮到了多線程的問題。它們的解決方案就是給加上線程鎖 ,我在數(shù)個(gè)開源的 GUI
框架看到他們都采用了這種解決方案。不過這樣做,在線程同步過程當(dāng)中,會(huì)產(chǎn)生與 GUI
框架邏輯不相關(guān)的同步消耗,雖然不是什么大不了的消耗,但是客戶可能因此就選擇了你的竟?fàn)帉?duì)手,如果線程竟?fàn)幖ち遥趶?qiáng)烈渴求資源的環(huán)境(如小型移動(dòng)設(shè)
置)當(dāng)中,這種消耗更是不可忽視的。
實(shí)際上在應(yīng)用當(dāng)中,極少有線程需要插入刪除其它線程創(chuàng)建的窗口映射(如果確實(shí)有這種需要,那么可以肯定項(xiàng)目的設(shè)計(jì)上出了問題)。在這種情況下本
線程創(chuàng)建窗口映射都將只是本線程存取,類似“Thread-Specific”的概念。也就是說,theWindowMap
當(dāng)中其它線程創(chuàng)建的窗口的映射對(duì)于本線程來說都是不需關(guān)心的,我們卻要為那部分不必要東西整天提心吊膽并付出運(yùn)行時(shí)消耗的代價(jià),這也有點(diǎn)像“穿著棉襖洗
澡”。但是怎么樣才能做到更輕松爽快些呢?
就本例問題而言,我們需要這樣一種變量來保存窗口映射,它針對(duì)每個(gè)線程有不同的值(Thread-Specific Data),這些值互不影響,并且所有線程對(duì)它的訪問如同是在訪問一個(gè)進(jìn)程內(nèi)的全局變量(Singelton)。
如果你是熟悉多線程編程的人,那么“Thread-Specific ”一定讓你想起了什么。是的,“Thread-Specific
Storage ” (線程相關(guān)存存諸,簡稱 TSS
),正是我們需要的,這是大多數(shù)操作系統(tǒng)都提供了的一種線程公共資源安全機(jī)制,這種機(jī)制允許以一定方式創(chuàng)建一個(gè)變量,這個(gè)變量在所在進(jìn)程當(dāng)中的每個(gè)線程當(dāng)
中,可以擁有不同的值。在 WIN32 上,這個(gè)變量就稱為“索引”,其相關(guān)的值則稱為“槽”, “Thread-Local
Storage”(線程局部存諸,簡稱 TLS )機(jī)制。它的提了供這樣幾個(gè)函數(shù)來定義,設(shè)置,讀取線程相關(guān)數(shù)據(jù)(關(guān)于 TLS 的更多信息,可以查閱
MSDN ):
//申請(qǐng)一個(gè)“槽”的索引。
DWORD TlsAlloc( void );
//獲得調(diào)用線程當(dāng)中指定“槽”的值。
VOID* TlsGetValue( DWORD dwTlsIndex );
//設(shè)置調(diào)用線程當(dāng)中指定“槽”的值。
BOOL TlsSetValue( DWORD dwTlsIndex,VOID* lpTlsValue );
//釋放掉申請(qǐng)的“槽”的索引
BOOL TlsFree( DWORD dwTlsIndex );
具體使用流程方法:先調(diào)用 TlsAlloc 申請(qǐng)一個(gè)“索引”,然后線程在適當(dāng)時(shí)機(jī)創(chuàng)建一個(gè)對(duì)象并調(diào)用 TlsSetValue
將“索引”對(duì)應(yīng)的“槽”設(shè)置為該對(duì)象的指針,在此之后即可用 TlsGetValue 訪問該“糟”。最后在不需要的時(shí)候調(diào)用 TlsFree
,如在本例當(dāng)中,調(diào)用 TlsFree 的最佳時(shí)機(jī)是在進(jìn)程結(jié)束時(shí)。
先封裝一下 TlsAlloc 和 TlsFree 以方便對(duì) ”索引“的管理。
class TlsIndex
{
public:
TlsIndex()
:m_index(::TlsAlloc())
{}
~TlsIndex()
{
::TlsFree(m_index);
}
public:
operator DWORD() const
{
return m_index;
}
private:
DWORD m_index;
};
如你所見,類 TlsIndex 將在構(gòu)造的時(shí)候申請(qǐng)一個(gè)“索引”,在析構(gòu)的時(shí)候釋放此“索引”。
在本例當(dāng)中 TlsIndex 的對(duì)象應(yīng)該存在進(jìn)程的生命周內(nèi),以保證在進(jìn)程退出之前,這個(gè)“索引”都不會(huì)被釋放,這樣的 TlsIndex
對(duì)象聽起來正像一個(gè)全局靜態(tài)對(duì)象,不過 Meyers Singelton
(用函數(shù)內(nèi)的靜態(tài)對(duì)象實(shí)現(xiàn))在這里會(huì)更適合,因?yàn)槲覀儾恍枰獙?duì)這個(gè)對(duì)象的生命周末進(jìn)行精確控制,只需要它在需要的時(shí)候創(chuàng)建,然后在進(jìn)程結(jié)束前銷毀即可。這
種方式只需要很少的代碼即可實(shí)現(xiàn),比如:
DWORD windowMapTlsIndex()
{
static TlsIndex s_ti; //提供自動(dòng)管理生命周期的“索引”
return s_ti;
}
利用這個(gè)“索引”,我們就能實(shí)現(xiàn)上述“Thread-Specific”的功能:
WindowMap* windowMap()
{
WindowMap* wp=reinterpret_cast<WindowMap*>(::TlsGetValue(windowMapTlsIndex()));
if(!wp)
{
wp=new WindowMap();
::TlsSetValue(windowMapTlsIndex(),wp);
}
return wp;
}
#define theWindowMap *(windowMap())
注意各線程訪問以上的代碼不會(huì)存在竟?fàn)帯_@樣就實(shí)現(xiàn)了一個(gè)線程安全且無線程同步消耗版本的“全局對(duì)象” theWindowMap 。我們甚至不用改變Window::create,Window::destory,queryWindow 的代碼,
這幾個(gè)簡單的函數(shù)看起來似乎不像一個(gè)“模式”,但是它確實(shí)是的。
現(xiàn)在總結(jié)一下“線程相關(guān)的單件模式”的概念:保證一個(gè)類在一個(gè)線程當(dāng)中只有一個(gè)實(shí)例,并提供一個(gè)訪問它的線程內(nèi)的訪問點(diǎn)的模式。
為了不重復(fù)地制造車輪,我將此類應(yīng)用的模式封裝了一下:
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 兩個(gè)函數(shù)設(shè)置為保護(hù)成員,是為了防止一些不三不四吊爾啷噹的程序隨意地刪除。
示例:
class WindowMapImpl:public TlsSingelton<WindowMap>
{
WindowMap m_map;
public:
WidnowMap& theWindowMapImpl()
{
return m_map;
}
public:
~WindowMapImpl();
protected:
WindowMapImpl(); //只能通過tlsCreate創(chuàng)建
friend class _Base;
};
#define theWindowMap (WindowMapImpl::tlsInstance()->theWindowMapImpl())
仍不需要修改原有窗口代碼。
posted on 2008-07-20 17:28
chatler 閱讀(121)
評(píng)論(0) 編輯 收藏 引用