1. 程式人生 > 其它 >執行緒異常終止的資源釋放問題

執行緒異常終止的資源釋放問題

執行緒異常終止的資源釋放問題

一般來說,Posix的執行緒終止有兩種情況:正常終止和非正常終止。執行緒主動呼叫pthread_exit()或者從執行緒函式中return都將使執行緒正常退出,這是可預見的退出方式;非正常終止是執行緒在其他執行緒的干預下,或者由於自身執行出錯(比如訪問非法地址)而退出,這種退出方式是不可預見的。

不論是可預見的執行緒終止還是異常終止,都會存在資源釋放的問題,在不考慮因執行出錯而退出的前提下,如何保證執行緒終止時能順利的釋放掉自己所佔用的資源,特別是鎖資源,就是一個必須考慮解決的問題。

最經常出現的情形是資源獨佔鎖的使用:執行緒為了訪問臨界資源而為其加上鎖,但在訪問過程中被外界取消,如果執行緒處於響應取消狀態,且採用非同步方式響應,或者在開啟獨佔鎖以前的執行路徑上存在取消點,則該臨界資源將永遠處於鎖定狀態得不到釋放。外界取消操作是不可預見的,因此的確需要一個機制來簡化用於資源釋放的程式設計。

在POSIX執行緒API中提供了一個pthread_cleanup_push()/pthread_cleanup_pop()函式對用於自動釋放資源 --從pthread_cleanup_push()的呼叫點到pthread_cleanup_pop()之間的程式段中的終止動作(包括呼叫 pthread_exit()和取消點(指cancel)終止)都將執行pthread_cleanup_push()所指定的清理函式。API定義如下:

void pthread_cleanup_push(void (*routine) (void  *),  void *arg)
void pthread_cleanup_pop(int execute)

pthread_cleanup_push()/pthread_cleanup_pop()採用先入後出的棧結構管理,void routine(void *arg)函式在呼叫pthread_cleanup_push()時壓入清理函式棧,多次對pthread_cleanup_push()的呼叫將在清理函式棧中形成一個函式鏈,在執行該函式鏈時按照壓棧的相反順序彈出。execute引數表示執行到pthread_cleanup_pop()時是否在彈出清理函式的同時執行該函式,為0表示不執行,非0為執行;這個引數並不影響異常終止時清理函式的執行。

pthread_cleanup_push()/pthread_cleanup_pop()是以巨集方式實現的,這是pthread.h中的巨集定義:

#define pthread_cleanup_push(routine,arg)                                     
  { struct _pthread_cleanup_buffer _buffer;                                   
    _pthread_cleanup_push (&_buffer, (routine), (arg));
#define pthread_cleanup_pop(execute)                                          
    _pthread_cleanup_pop (&_buffer, (execute)); }

可見,pthread_cleanup_push()帶有一個"{",而pthread_cleanup_pop()帶有一個"}",因此這兩個函式必須成對出現,且必須位於程式的同一級別的程式碼段中才能通過編譯。在下面的例子裡,當執行緒在"do some work"中終止時,將主動呼叫pthread_mutex_unlock(mut),以完成解鎖動作。

在“do some work"中終止時,將主動呼叫pthread_mutex_unlock(mut),以完成解鎖動作。

pthread_cleanup_push(pthread_mutex_unlock, (void *) &mut);
pthread_mutex_lock(&mut);
/* do some work */
pthread_mutex_unlock(&mut);
pthread_cleanup_pop(0);

本來do some work之後是有pthread_mutex_unlock(&mut);這句,也就是有解鎖操作,但是在do some work時會出現非正常終止,那樣的話,系統會根據pthread_cleanup_push中提供的函式,和引數進行解鎖操作或者其他操作,以免造成死鎖!

必須要注意的是,如果執行緒處於PTHREAD_CANCEL_ASYNCHRONOUS狀態,上述程式碼段就有可能出錯,因為CANCEL事件有可能在pthread_cleanup_push()和pthread_mutex_lock()之間發生,或者在pthread_mutex_unlock()和pthread_cleanup_pop()之間發生,從而導致清理函式unlock一個並沒有加鎖的mutex變數,造成錯誤。因此,在使用清理函式的時候,都應該暫時設定成PTHREAD_CANCEL_DEFERRED模式。為此,POSIX的Linux實現中還提供了一對不保證可移植的pthread_cleanup_push_defer_np()/pthread_cleanup_pop_defer_np()擴充套件函式,功能與以下程式碼段相當:

{ 
    int oldtype;
    pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);
    pthread_cleanup_push(routine, arg);
    ...
    pthread_cleanup_pop(execute);
    pthread_setcanceltype(oldtype, NULL);
}

補充:線上程宿主函式中主動呼叫return,如果return語句包含在pthread_cleanup_push()/pthread_cleanup_pop()對中,則不會引起清理函式的執行,反而會導致segment fault。