天天看點

線程資源釋放 問題解決

一般來說,Posix的線程終止有兩種情況:正常終止和非正常終止。線程主動調用pthread_exit()或者從線程函數中return都将使線程正常退出,這是可預見的退出方式;非正常終止是線程在其他線程的幹預下,或者由于自身運作出錯(比如通路非法位址)而退出,這種退出方式是不可預見的。

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

最經常出現的情形是資源獨占鎖的使用:線程為了通路臨界資源而為其加上鎖,但在通路過程中被外界取消,如果線程處于響應取消狀态,且采用異步方式響應,或者在打開獨占鎖以前的運作路徑上存在取消點,則該臨界資源将永遠處于鎖定狀态得不到釋放。外界取消操作是不可預見的,是以的确需要一個機制來簡化用于資源釋放的程式設計。

在POSIX線程API中提供了一個pthread_cleanup_push()/pthread_cleanup_pop()函數對用于自動釋放資源。

–從pthread_cleanup_push()的調用點到pthread_cleanup_pop()之間的程式段中的終止動作(包括調用 pthread_exit()和取消點終止)都将執行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),以完成解鎖動作。

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

必須要注意的是,如果線程處于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);
 }
           

繼續閱讀