2013年7月8日
#
在Linux系統中,有兩種不同的上下文:進程上下文、中斷上下文。
在中斷中沒有進程上下文,而具有中斷上下文,因此在中斷上下文中不能發生睡眠,也就是不能發生進程切換。
這就決定了在在中斷上下文中不能采用同步原語(信號量,管程、同步變量等)和長期互斥原語(這會導致進程睡眠), 而只能采用短期互斥原語(例如自旋鎖)。
曾經,中斷處理程序并不具有自己的棧。相反,它們共享所中斷進程的內核棧。內核棧的大小是兩頁,具體地說,在32位體系結構上是8KB,在64位體系結構上是16KB.
現在。中斷處理程序有了自己的棧,每個處理器一個,大小為一頁。這個棧就稱為中斷棧,盡管中斷棧的大小是原先共享棧的一半,但平均可用棧空間大得多,因為中斷處理
程序把這一整頁占為己有。
UP(單CPU系統)上的中斷處理
互斥
如果一個中斷處理程序的代碼訪問或者更新了由非中斷的代碼(通常稱為基準代碼)使用的同一數據結構,那么就會出現競爭條件。
幸運的是,得到允許的以內核態執行的進程會臨時禁止中斷。因此,只要基準代碼要更新一個與中斷處理程序共享的數據結構,那么就
首先禁止中斷,執行臨界段,然后再重新允許中斷。禁止和允許中斷的動作就實現了互斥。
在采取中斷互斥時,必須使用函數顯示地把編碼寫入算法中。
MP(多CPU系統)上
在MP系統上,中斷可以在任何處理器上出現。從最低限度上來說,每個進程會接收時鐘中斷,但也可能接收I/O中斷。在MP系統上,例程
SPL(禁止中斷)所提供的保護并不充分,因為它們執行影響執行它們的處理器上的中斷優先級。中斷可能會在另一個處理器上出現,如果設備驅動程序
正在別處運行,那么會造成一個競爭條件。因為中斷處理程序代表另一個進入內核的入口點。
當基準驅動程序代碼和中斷處理程序之間共享數據結構時,UP可以通過屏蔽中斷來防止出現競爭條件的技術,在多線程MP內核中還不充分。
臨界段要在一個處理器上執行,執行屏蔽中斷例程只會屏蔽在那個處理器上出現的中斷。如果在別的處理器上出現中斷,那么立即就會有
兩個處理器同時訪問、而且可能更新臨界資源。既然這些臨界段需要短期互斥,那么可以使用自旋鎖來對數據進行保護。
如果不考慮中斷處理程序和基準代碼之間的互斥,則Linux中的中斷處理程序是無須重入的。當一個給定的中斷處理程序正在執行時,相應的中斷線
在所有處理器上都會被屏蔽掉,以防止同一中斷線上接收另一個新的中斷。通常情況下,所有的其他中斷都是打開的,所以這些不同中斷線上的其他中斷
都能處理,但當前中斷線總是被禁止的。由此可以看出,同一個中斷處理程序絕不會被同時調用以處理嵌套的中斷。這極大地簡化了中斷程序的編寫。
2013年6月20日
#
MP(多CPU)同步原語代碼示例
----引自《現代體系結構上的UNIX系統》
為了便于對示例的展開,我們首先假定一個場景:
內核中把檢測到的錯誤信息記錄到一個磁盤文件中的情形。出錯信息通過內存中的一個隊列來傳遞給日志進程(logging process)的。
當出現一個錯誤時,就在隊列中加入一項,并且通過調用函數log_error 通知日志進程。出錯日志進程接著把隊列中的項寫到磁盤上。
這就使得碰到錯誤的進程不必等候I/O完成或者或得為了向文件執行I/O而可能需要的任何鎖,并且避免了任何可能的上鎖次序問題。
代碼1, 采用事件計數的出錯日志通知機制
日志進程
log_error(error) |
{ | for(next=1; ; next++) {
lock(&err_queue); | await(&err_event, next);
把出錯信息加入到隊列 | lock(&err_queue);
unlock(&err_queue); | 從隊列中刪除項
advance(&err_event); | unlock(&err_queue);
} | write error to disk
| }
隊列本身由一個自旋鎖來保護。在本例中,事件計數只用于同步的目的,并不提供互斥。
在試用事件計數的時候,advance操作會永久性地改變事件計數的狀態。advance和await操作的相對時序沒有關系。
代碼2, 采用同步變量的出錯日志通知機制
日志進程
log_error(error) |
{ | for(;;){
lock(&err_queue); | lock(&err_queue);
把出錯信息加入到隊列 | if (queue_empty){
SV_SIGNAL(&err_syncvar, 0); | SV_WAIT(&err_syncvar, PRI, &err_queue);
unlock(&err_queue); | lock(&err_queue);
} | }
| 從隊列中刪除項
| unlock(&err_queue);
| 把錯誤寫入磁盤
| }
因為同步變量自身沒有保留狀態,所以當日志進程測試隊列的狀態并決定是等待一項還是從隊列中刪除一項的時候,必須占有自旋鎖。類似地,log_error在
發送信號時也必須占有自旋鎖。注,SV_WAIT將釋放自旋鎖,并且阻塞日志進程,SV_SIGNAL到后從阻塞處繼續執行。
代碼3, 采用管程的出錯日志通知機制
日志進程
log_error(error) | for(;;){
{ | mon_enter(&err_mon);
mon_enter(&err_mon); | if (queue empty)
把出錯信息加入到隊列 | mon_wait(&err_mon, NEWENTRY);
|
mon_signal(&err_mon, NEWENTRY); | 從隊列中刪除項
mon_exit(&err_mon); | mon_exit(&err_mon);
} | 把錯誤寫入磁盤
| }
代碼4, 采用信號量的出錯日志通知機制
日志進程
log_error(error) | for(;;){
{ | P(&err_sema);
lock(&err_queue); | lock(&err_queue);
把出錯信息加入到隊列 | 從隊列中刪除項
unlock(err_queue); | unlock(&err_queue);
V(&err_sema); | 把錯誤寫入磁盤
} | }
我們把異步環境下的一組并發進程因直接制約而互相發送消息、進行互相合作、互相等待,使得各進程按一定的速度執行的過程稱為進程間的同步。
具有同步關系的一組并發進程稱為合作進程,合作進程間互相發送的信號稱為消息或事件。 如果我們對一個消息或事件賦以唯一的消息名,則我們
可用過程 wait (消息名) 表示進程等待合作進程發來的消息,而用過程 signal (消息名) 表示向合作進程發送消息。
(引自百度百科)
進程間的同步方式:
為了實現進程互斥地進入自己的臨界區,操作系統中設置專門的同步機制來協調各進程間的運行。所有的同步機制都應遵循下書四條準則:
1)空閑讓進
2)忙則等待
3)有限等待
4)讓權原則。當進程不能進入自己的臨界區時,應立即釋放處理機,以免進程陷入“忙等”狀態。
1.單CPU (UP)機器利用sleep/wakeup函數對實現同步機制。
函數sleep是一個內部的內核例程,它掛起調用它的進程,直到指定的事件發生為止。這是一個以內核態運行的進程自愿出讓控制權,允許自己被搶占。
函數wakeup用于發出一個特定事件已經出現的信號,它使得所有等待該事件的進程被喚醒,并放回到運行隊列中。事件用一個整數值來表示,它往往
是該事件相關的內核數據結構的地址。
void lock_object( char *flag_ptr)
{
lock(&object_locking); //自旋鎖
while (*flag_ptr)
sleep(flag_ptr);
*flag_ptr = 1;
unlock(&object_locking);
}
void unlock_object( char *flag_ptr)
{
lock( &object_locking );
*flag_ptr = 0;
wakeup( flag_ptr);
unlock( &object_locking );
}
應為wakeup操作沒有記憶,所以wakeup函數必須喚醒在同一事件上睡眠的所有進程。在多CPU系統上,即MP上sleep/wakeup機制不起作用。
2.SVR4.2 MP 提供了單獨的執行進程同步的原語:同步變量。
因為同步變量不包含狀態,所以可以把它們想成是sleep/wakeup的一種MP變形。相反,所需的任何狀態信息都保存在外部標志或者計數器中。
同步變量的設計要同自旋鎖配合工作。
同步變量被聲明為sv_t類型,采用下面的函數可以給它分配空間和進行初始化:
sv_t *SV_ALLOC( int slpflag);
slpflag指定,如果需要為同步變量分配內存,那么是否能阻塞進程。
回收同步變量可以調用
void SV_DEALLOC( sv_t *svp );
內核希望單獨等候的每一個事件都用一個不同的同步變量來表示,這就好比配合sleep如何使用唯一的事件參數。
void SV_WAIT( sv_t *svp, int pri, lock_t *lockp );
要觸發在同步變量上的事件,可以使用下面的函數:
void SV_SIGNAL( sv_t *svp, int flags);
SV_SIGNAL與wakeup的相似之處在于,如果沒有正在睡眠的進程,那么就對過去曾經執行過的操作沒有記憶,調用什么也不做。
SV_SIGNAL只喚醒一個進程。如果要喚醒在事件上睡眠的所有進程,可以用同步變量的下列函數來實現:
void SV_BROADCAST( sv_t *svp, int flags);
如果在事件被觸發之前出現了一個UNIX信號,那么下面的SV_WAIT變形會喚醒進程:
bool_t SV_WAIT_SIG( sv_t *svp, int pri, lock_t *lkp );
返回的代碼表明發生了什么樣的事件:如果出現了一個UNIX信號,那么它返回FALSE,如果出現了SV_SIGNAL或SV_BROADCAST,那么它返回TRUE.
3.采用信號量的同步
將信號量的值初始化為0,就可以用于進程同步,這樣允許通過使用P操作讓一個進程等待某個事件發生。既然信號量被初始化為0,那么進程將立即阻塞。
另一個進程使用V操作能夠發出信號,表明事件已經結束。V操作導致正等待事件的進程被喚醒,并繼續進行。因為即使在信號量上沒有阻塞進程,
V操作也會給信號量加1,所以在前一個進程能夠執行P操作之前出發事件會導致進程繼續進行,不必等待。這是一種受歡迎的情形,因為它不需要額外的
協調工作,就能夠處理在等候事件的進程同發信號表明該事件完成的進程之間本來就有的競爭條件。
進程1 進程2
p(s) /*等待事件*/ .
.
.
V(s) /*觸發事件*/
4.利用管程進行同步
管程為臨界資源以及訪問或者修改該資源的所有臨界段提供了互斥機制,它還提供了在使用管程的諸進程之間進行同步的手段。一個管程可以想成是一個裝有
資源的隔間。進程要訪問資源,它必須首先進入隔間。通過一次只允許一個進程進入隔間,就做到了互斥。如果在管程已經投入使用的時候,別的進程試圖進
入它,那就會被阻塞,直到使用管程的進程退出管程為止,或者在與管程關聯的事件上等待。每個管程都可能有一個或者更多的事件,若干進程能夠在這些事
件上等待。進程被阻塞這些事件上,直到在管程內執行的其他進程觸發事件為止。根據定義,觸發操作只能從管程內部完成。
5.利用事件計數進行同步
事件計數是一個非遞減的正整數,在這個數值上定義了3種操作。操作advance(E)將事件計數E加1,這叫做出發事件。
操作await(E,V)致使調用進程被阻塞,指導事件計數E的值達到V為止。如果在調用await的時候,事件計數的值大于或等于V,那么進程繼續執行,而不會阻塞,
因為事件是以前觸發的。事件計數的當前值可以用read(E)來讀取。在創建事件計數的時候,它被初始化為0,而且在數值上永遠不會減少。假定保存事件計數值
得存儲器位置足夠大,于是事件計數在其整個生命期中,一直都不會溢出(通常一個32位的無符號整數就夠了)。
有關代碼示例,請參見后面的隨筆。
2013年6月8日
#
在多CPU系統,即MP系統中,存在總線仲裁。
1. 原子操作
從CPU或者I/O設備到主存儲器的單次讀或者寫操作為原子操作。
這樣的操作一旦開始,就不能被系統上來自CPU或者I/O設備的任何其他存儲操作所中斷,或者受到他們的干擾。
原子變量
2. 自旋鎖 (自旋鎖用于短期互斥)
自旋鎖得名于這樣一個事實,一個進程在等候另一個進程正在使用的鎖時會處于忙等待(busy-wait,在一個循環中自旋)狀態。
typedef int lock_t;
void initlock( volatile lock_t * lock_status)
{
*lock_status = 0;
}
int
test_and_set(volatile int *addr)
{
int old_value;
old_value = swap_atomic(addr, 1);
if (old_value == 0)
return 0;
return 1;
}
void lock(volatile lock_t *lock_status)
{
while (test_and_set( lock_status) == 1) //被鎖定時,進程在此自旋。
;
}
// test_and_set 如果前面的狀態不為0就返回1,否則返回0.
//如果鎖的狀態已經是1(鎖已經被占用),那么test_and_set函數返回1,并且處理器在循環中自旋,直到該鎖被釋放為止。只要把鎖的狀態設置為0,就可以釋放鎖了。
void
unlock(volatile lock_t * lock_status)
{
*lock_status =0;
}
減少對鎖的爭用可以采用兩種辦法:
第一、內核針對不同的臨界資源使用不同的自旋鎖,防止處理器在沒有競爭條件威脅的時候被另一個處理器掛起。
第二、增強lock和unlock函數,在上鎖的時候屏蔽中斷。
在平時的應用設計中,由于出現多線程(進程)的并發應用,以及分布式應用,比較容易出現死鎖現象。
下面來看一個簡單的實例:
有兩個獨立的鏈表,假定使用兩個獨立的鎖來保護他們,以便能夠獨立的訪問。再假定某個操作要求遍歷一個列表所檢索到的元素必須從兩個列表中斷開連接,
而且必須以一次
原子操作來完成。
線程1 | 線程2
lock(&lock_a); | lock(&lock_b);
find element to unlink on list a | find element to unlink on list b
lock(&lock_b); | lock(&lock_a);
unlink element from both lists | unlink element from both lists
unlock(&lock_b); | unlock(&lock_a);
unlock(&lock_a); | unlock(&lock_b);
可能出現死鎖的情況。
這要求在消除一個元素是必須同時擁有兩個列表的鎖。對第一個線程要先或得鎖lock_a, 然后要或得鎖lock_b.
第2個線程正好相反,它先或得鎖lock_b,然后再獲取鎖lock_a.
如果某一時刻,第一個線程執行到了 find element to unlink on list a, 而第二個線程此時執行到了find element to unlink on list b, 則這兩個線程將發生死鎖。
這種死鎖稱為AB-BA死鎖。 (注,死鎖的發生是跟兩個線程的執行時序相關的,例如,第一個線程執行完了所有的這部分代碼,線程2才開始執行此段代碼,則不會發生死鎖。)
如果某個鎖不是遞歸鎖,例如lock_a, 而線程1在應用中對它進行多個調用,而沒有調用解鎖操作,也會發生死鎖。
代碼示例如下:
lock(&lock_a);
other logic code
lock(&lock_a);
other logic code
unlock(&lock_a);
unlock(&lock_a);
防止死鎖的辦法: 為了防止發生這類死鎖,所有線程必須以相同的次序獲得嵌套鎖,即以相同的次序獲得且同時占有鎖。
可以把上面的代碼改成如下代碼來避免死鎖:
lock(&lock_a);
lock(&lock_b);
find element to unlink on list a or b
unlink element from both lists
unlock(&lock_b);
unlock(&lock_a);
當涉及到3個或者更多鎖的時候也是如此:只要各線程在獲得和釋放鎖的時候保持相同的次序,那么就不會出現死鎖。
2009年3月20日
#
實體(語言內建的數據類型,開發者定義的類和方法等)的定義與聲明,實體和指針跟程序設計帶來了不同影響.
對于實體或定義,編譯器要知道實體真實的物理內存布局,因此讓編譯器知道這些信息,并且在程序編譯完畢后不能更改.要想更改必須重新編譯程序.因此如果在系統設計者程序庫中運用了inline函數,并且如果應用開發者在應用中用了這個inline函數,則當后來要對inline進行修改時,有可能要導致應用被重新編譯.
對于指針,它的大小在特定的機器上是固定的(在32位機器上,它的大小是32位;在64位機器上,它的大小是64位).因此可以改變它的值,而不需要重新編譯應用,就可以改變應用的功能.
在面向對象中,可以通過虛函數指針來延遲特定函數的決策,即調用子類的函數.
在C語言中,我們可以通過函數指針來對函數的功能進行推遲決策.
在C++中,我們也可以通過函數指針(函數對象)、對象指針來推遲決策,從而使程序的功能更有彈性。例如,在設計模式中的strategy模式中,就是通過在contex中包含一個指向strategy的指針來實現的。我們可以定義一個抽象的strategy接口,然后由各個具體的strategy實現這些接口,從而在保證應用架構幾乎不做任何調整下,實現不同的功能。當然在這種實現方式中,我們應該加入strategy的決議者,由它來裁決采用哪一種策略方式。決議者可以采用配置文件、應用的輸入等作為決議的依據。
熟悉symbian的人,很快就會發現:它與symbian中的ECOM架構很相似。它要求各種strategy的實現方式被包含在共享的DLL中,并由RLibrary::Loard()調用動態載入.
使用定義抽象接口,然后在各DLL中定義具體的實現,并且動態載入,我們可以比較容易地實現所謂的插件(plugin)。插件的載入取決于配置,或相應的輸入檢測。
下面給出在linux和windows上從動態庫中查找和載入的例子:
#ifdef WIN32
HINSTANCE hDll;
if(!(hDll = LoadLibrary(VOCALSIP_DLLPATH)))
adapter.m_initFunc = (INIT_PROTOSTACK_FUNC)GetProcAddress( hDll, "InitVocalSipStack");
adapter.m_createFunc = (CREATE_CHANNEL_FUNC)GetProcAddress( hDll, "CreateVocalSipGCChannel");
adapter.m_cleanupFunc = (CLEANUP_PROTOSTACK_FUNC)GetProcAddress( hDll, "CleanupVocalSipStack");
#else
void* h_dl = dlopen(VOCALSIP_DLLPATH,RTLD_NOW | RTLD_GLOBAL);
adapter.m_initFunc = (INIT_PROTOSTACK_FUNC)dlsym( h_dl, "InitVocalSipStack");
adapter.m_createFunc = (CREATE_CHANNEL_FUNC)dlsym( h_dl, "CreateVocalSipGCChannel");
adapter.m_cleanupFunc = (CLEANUP_PROTOSTACK_FUNC)dlsym( h_dl, "CleanupVocalSipStack");
#endif
2009年3月18日
#
"互斥(mutext)和旗語(semaphore)之間有什么不同?"這樣的問題簡短而有力,但要回答卻相當困難.即使有經驗的實時操作系統(RTOS)用戶在區別如何正確使用mutex和semaphore時也存在著困難.
但這一點很不幸而且很危險,因為無任這兩種原生RTOS中的哪一種被錯誤使用,都會導致嵌入式系統出現意想不到的錯誤,特別是這些系統為有關生命安全的產品時.
有關mutex和semaphore的荒誕說法是它們是相似的,甚至是可以互換的.正確的事實是盡管mutex和semaphore在它們的執行上有相似之處,但是我們還是應該在使用它們時加以區別對待.
最普遍(但也是不正確)的答案是:mutex和semphore非常相似,它們只有一個區別,那就是semaphores的計數可以超過1. 差不多所有的工程師都能正確的理解:mutex是一個二進制標志,可以通過它來確保執行流在代碼關鍵區(critical section of code)互相排斥,從而對共享資源加一保護.但當他們被要求進一步回答如何使用"計算方法semaphore"的方式時,大部分工程師的回答就如同教科書書一般的刻板---semaphore用于保護多重同類資源.
通過類比辦法,我們很容易解釋為什么"多重資源"場景是有缺陷的.如果你認為一個mutex是由操作系統擁有的關鍵值的話,我們可以很容易地將個別的mutex比喻是城市咖啡店中一間浴室的鑰匙.如果你想使用浴室,卻找不到鑰匙,你就必須在一個隊列中等候.同樣地,mutex則協串行化多項任務,以取得全域資源的共享,并且為等待隊列中的任務分配一個靜候其循序漸進的位置.
但這種簡單的資源保護協議并不使用于兩間相同浴室的情況.如果把一個semaphore概括為一個mutex,使其能保護兩個或更多相同的資源,那么在我們的比喻中,它就象是放著兩把相同鑰匙的藍子,你可以用任何一把打開任何一扇浴室的門.
因此,semaphore本身并不能解決多個相同資源的問題.咖啡店中的客人可能只知道有一把鑰匙,但并不知道哪間浴室可用.如果你試圖以此方式使用semaphore,你將會發現需要更多的狀態信息---它們通常是由不同的mutex所保護的共享資源.
正確使用semaphore是為了使信號從一項任務傳至另一項任務.mutex意味著取得與釋放,使用受保護共享資源的每一次任務都是以這樣的順序進行.相比之下,使用semaphore的任務通常不是發送信號,就是進入等待狀態,不可能同時發生.
例如,任務1可能包含程序代碼,當按下"電源"(power)按鈕時,即可提出(如發送信號或增量)一個特別的semaphore; 任務2則依據相同的semaphore而用于喚醒顯示器. 在這種情況下,其中一項任務是信號的生產者,另一項任務是信號的消費者.
用一個例子來做總結,首先展示如何使用mutex:
/* Task 1 */
mutexWait(mutex_mens_room);
// Safely use shared resource
mutexRelease(mutex_mens_room);
/* Task 2 */
mutexWait(mutex_mens_room);
// Safely use shared resource
mutexRelease(mutex_mens_room);
相應地,你總是采用下列方法使用semaphore:
/* Task 1 - Producer */
semPost(sem_power_btn); // Send the signal
/* Task 2 - Consumer */
semPend(sem_power_btn); // Wait for signal
重要的是,semaphores可以被interrupt service routine(ISR)中斷服務程序用來向task發送信號.發送一個semaphore是一個非阻塞的RTOS行為,并且ISR安全.因為這種技術排除了在task級別的為了是中斷不使能而引起的錯誤的可能性,從ISR中發出信號是一種使嵌入式軟件更加可靠的設計方式.
http://www.embedded.com/columns/guest/210605040?printable=true
http://www.eettaiwan.com/ART_8800557420_676964_NT_a22f6436.HTM
2009年3月17日
#
摘要: 前一段時間,讀symbian文檔資料,和Darwin,ICE等開源代碼,經常碰到定時器和定時器事件.故對定時器的實現進行了一些整理,作為讀書筆記,以防以后忘記.
閱讀全文
2009年3月11日
#
在以前的multi-process程序中,process之間通過共享內存、操作系統提供的消息對列,命名管道等不同方式進行數據傳遞。為了減少內存,以及進程切換時引發的上下文切換的開銷,現在的系統一般采用multi-thread 工作方式。
各process都有各自獨立的地址空間,并且是專有的,因此一個process不能通過指針訪問另一個process中的地址空間。而屬于同一process的各thread,它們共享同一process的地址空間,因此一個thread可以通過指針訪問另一個thread中的地址。這樣我們可以在應用中自己定義消息隊列,并對消息隊列的訪問進行管理,簡化程序并提高性能。
在multi-thread應用中,我們可以使用消息對列來在不同thread之間進行消息傳遞,并降低各thread之間的藕合。
它們之間的關系如下:
生產者:向消息隊列中放數據的線程。
消費者:從消息隊列中取出數據的線程。
生產者 ----> 消息隊列 ----->消費者
如果應用規定消息隊列的最大長度。在對隊列進行控制時,應該當消息隊列滿時,可能要讓生產者進行等待,直到消息隊列中有新的位置可以放入新的消息。當消息隊列為空時,應該讓消費者進行等待,知道有新的消息被加到消息隊列中。
在該模型中有兩點需要注意:
1.不同生產者,消費者同時并發操作消息隊列時,對各操作的串行化。這個問題可以通過對隊列的操作進行加鎖來實現。它的實現可以參考另一篇隨筆《C++同步鎖管理的一種方法》。
2.在消息隊列為空或滿時,應用程序的處理。
這可以在C++中采用Monitor.在Monitor中對某種條件進行監控。
Monitor 對象可以采取的動作:
(1)、等待操作 wait(long timeout = INFINITE), 缺省情況下無限等待下去。
(2)、發信號操作 signal(long count = 1), 缺省情況下激活一個正在消息隊列上進行等代的線程。
對于Monitor的實現,可以很簡單地用下列兩種方式實現:
1、采用ACE中的ACE_Event, ACE_Thread_Semphore實現。
2、采用 ICE的Cond實現, 在Linux上,Cond實際上是通過pthread_cond_t來實現的。
2009年3月9日
#
前段時間,寫了一點關于智能指針的東西,有讀者反映沒有代碼比較難懂.現給出源碼,并稍微加以解釋.
智能指針類用到的基類的定義:
template<typename T>
class HandleBase
{
public:
typedef T element_type;
T* get() const
{
return _ptr;
}
//重載->操作符,返回所指對象的指針.
T* operator->() const
{
if(!_ptr)
{
//
// We don't throw directly NullHandleException here to
// keep the code size of this method to a minimun (the
// assembly code for throwing an exception is much bigger
// than just a function call). This maximises the chances
// of inlining by compiler optimization.
//
throwNullHandleException(__FILE__, __LINE__);
}
return _ptr;
}
// 通過智能指針獲取所指對象的引用.
T& operator*() const
{
if(!_ptr)
{
//
// We don't throw directly NullHandleException here to
// keep the code size of this method to a minimun (the
// assembly code for throwing an exception is much bigger
// than just a function call). This maximises the chances
// of inlining by compiler optimization.
//
throwNullHandleException(__FILE__, __LINE__);
}
return *_ptr;
}
operator bool() const
{
return _ptr ? true : false;
}
void swap(HandleBase& other)
{
std::swap(_ptr, other._ptr);
}
T* _ptr;
private:
void throwNullHandleException(const char *, int) const;
};
......
// 智能指針類定義
template<typename T>
class Handle : public HandleBase<T>
{
public:
Handle(T* p = 0) //智能指針的構造函數
{
this->_ptr = p;
if(this->_ptr)
{
this->_ptr->__incRef(); //在構造函數中增加所指對象的引用計數
}
}
template<typename Y> //拷貝構造函數
Handle(const Handle<Y>& r)
{
this->_ptr = r._ptr;
if(this->_ptr)
{
this->_ptr->__incRef(); //在構造函數中增加所指對象的引用計數
}
}
Handle(const Handle& r) //拷貝構造函數
{
this->_ptr = r._ptr;
if(this->_ptr)
{
this->_ptr->__incRef(); //在構造函數中增加所指對象的引用計數
}
}
~Handle()
{
if(this->_ptr)
{
this->_ptr->__decRef(); //在析構函數中減少所指對象的引用計數
}
}
// 重載=操作符, 要注意所有權 (即,對原實例的處理).
Handle& operator=(T* p)
{
if(this->_ptr != p)
{
if(p)
{
p->__incRef(); //增加新指對象的引用計數
}
T* ptr = this->_ptr;
this->_ptr = p;
if(ptr)
{
ptr->__decRef(); //減少原來所指對象的引用計數
}
}
return *this;
}
template<typename Y>
Handle& operator=(const Handle<Y>& r)
{
if(this->_ptr != r._ptr)
{
if(r._ptr)
{
r._ptr->__incRef(); //增加新指對象的引用計數
}
T* ptr = this->_ptr;
this->_ptr = r._ptr;
if(ptr)
{
ptr->__decRef(); //減少原來所指對象的引用計數
}
}
return *this;
}
Handle& operator=(const Handle& r)
{
if(this->_ptr != r._ptr)
{
if(r._ptr)
{
r._ptr->__incRef(); //增加新指對象的引用計數
}
T* ptr = this->_ptr;
this->_ptr = r._ptr;
if(ptr)
{
ptr->__decRef(); //減少原來所指對象的引用計數
}
}
return *this;
}
跟智能指針配合使用的對象.要能夠跟指針智能配合使用,這些對象應該是從下列類的派生類的實例.
class SimpleShared
{
public:
SimpleShared();
SimpleShared(const SimpleShared&);
virtual ~SimpleShared()
{
}
SimpleShared& operator=(const SimpleShared&)
{
return *this;
}
void __incRef()
{
assert(_ref >= 0);
++_ref;
}
void __decRef()
{
assert(_ref > 0);
if(--_ref == 0) // 如果引用計數為0,則摧毀對象本身.
{
if(!_noDelete)
{
_noDelete = true;
delete this;
}
}
}
int __getRef() const
{
return _ref;
}
void __setNoDelete(bool b)
{
_noDelete = b;
}
private:
int _ref;
bool _noDelete;
};
class Shared
{
public:
Shared();
Shared(const Shared&);
virtual ~Shared()
{
}
Shared& operator=(const Shared&)
{
return *this;
}
virtual void __incRef();
virtual void __decRef();
virtual int __getRef() const;
virtual void __setNoDelete(bool);
protected:
#if defined(_WIN32)
LONG _ref;
#elif defined(ICE_HAS_ATOMIC_FUNCTIONS)
volatile int _ref;
#else
int _ref;
Mutex _mutex;
#endif
bool _noDelete;
};