線程取消(pthread_cancel)
基本概念
pthread_cancel調用並不等待線程終止,它只提出請求。線程在取消請求(pthread_cancel)發出後會繼續運行,
直到到達某個取消點(CancellationPoint)。取消點是線程檢查是否被取消並按照請求進行動作的一個位置.
與線程取消相關的pthread函數
int pthread_cancel(pthread_t thread)
發送終止信號給thread線程,如果成功則返回0,否則為非0值。發送成功並不意味著thread會終止。
int pthread_setcancelstate(int state, int *oldstate)
設置本線程對Cancel信號的反應,state有兩種值:PTHREAD_CANCEL_ENABLE(缺省)和PTHREAD_CANCEL_DISABLE,
分別表示收到信號後設為CANCLED狀態和忽略CANCEL信號繼續運行;old_state如果不為NULL則存入原來的Cancel狀態以便恢復。
int pthread_setcanceltype(int type, int *oldtype)
設置本線程取消動作的執行時機,type由兩種取值:PTHREAD_CANCEL_DEFFERED和PTHREAD_CANCEL_ASYCHRONOUS,僅當Cancel狀態為Enable時有效,分別表示收到信號後繼續運行至下一個取消點再退出和立即執行取消動作(退出);oldtype如果不為NULL則存入運來的取消動作類型值。
void pthread_testcancel(void)
是說pthread_testcancel在不包含取消點,但是又需要取消點的地方創建一個取消點,以便在一個沒有包含取消點的執行代碼線程中響應取消請求.
線程取消功能處於啟用狀態且取消狀態設置為延遲狀態時,pthread_testcancel()函數有效。
如果在取消功能處處於禁用狀態下調用pthread_testcancel(),則該函數不起作用。
請務必僅在線程取消線程操作安全的序列中插入pthread_testcancel()。除通過pthread_testcancel()調用以編程方式建立的取消點意外,pthread標準還指定了幾個取消點。測試退出點,就是測試cancel信號.
取消點:
線程取消的方法是向目標線程發Cancel信號,但如何處理Cancel信號則由目標線程自己決定,或者忽略、或者立即終止、或者繼續運行至Cancelation-point(取消點),由不同的Cancelation狀態決定。
線程接收到CANCEL信號的缺省處理(即pthread_create()創建線程的缺省狀態)是繼續運行至取消點,也就是說設置一個CANCELED狀態,線程繼續運行,只有運行至Cancelation-point的時候才會退出。
pthreads標準指定了幾個取消點,其中包括:
(1)通過pthread_testcancel調用以編程方式建立線程取消點。
(2)線程等待pthread_cond_wait或pthread_cond_timewait()中的特定條件。
(3)被sigwait(2)阻塞的函數
(4)一些標準的庫調用。通常,這些調用包括線程可基於阻塞的函數。
缺省情況下,將啟用取消功能。有時,您可能希望應用程序禁用取消功能。如果禁用取消功能,則會導致延遲所有的取消請求,
直到再次啟用取消請求。
根據POSIX標準,pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函數以及
read()、write()等會引起阻塞的系統調用都是Cancelation-point,而其他pthread函數都不會引起Cancelation動作。
但是pthread_cancel的手冊頁聲稱,由於LinuxThread庫與C庫結合得不好,因而目前C庫函數都不是Cancelation-point;但CANCEL信號會使線程從阻塞的系統調用中退出,並置EINTR錯誤碼,因此可以在需要作為Cancelation-point的系統調用前後調用pthread_testcancel(),從而達到POSIX標準所要求的目標.
即如下代碼段:
pthread_testcancel();
retcode = read(fd, buffer, length);
pthread_testcancel();
註意:
程序設計方面的考慮,如果線程處於無限循環中,且循環體內沒有執行至取消點的必然路徑,則線程無法由外部其他線程的取消請求而終止。因此在這樣的循環體的必經路徑上應該加入pthread_testcancel()調用.
取消類型(Cancellation Type)
我們會發現,通常的說法:某某函數是 Cancellation Points,這種方法是容易令人混淆的。
因為函數的執行是一個時間過程,而不是一個時間點。其實真正的 Cancellation Points 只是在這些函數中 Cancellation Type 被修改為 PHREAD_CANCEL_ASYNCHRONOUS 和修改回 PTHREAD_CANCEL_DEFERRED 中間的一段時間。
POSIX的取消類型有兩種,一種是延遲取消(PTHREAD_CANCEL_DEFERRED),這是系統默認的取消類型,即在線程到達取消點之前,不會出現真正的取消;另外一種是異步取消(PHREAD_CANCEL_ASYNCHRONOUS),使用異步取消時,線程可以在任意時間取消。
線程終止的清理工作
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() 的調用將在清理函數棧中形成一個函數鏈;
從pthread_cleanup_push的調用點到pthread_cleanup_pop之間的程序段中的終止動作(包括調用pthread_exit()和異常終止,不包括return)
都將執行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(0); 或者 void cleanup(void *arg) { pthread_mutex_unlock(&mutex); } void* thread0(void* arg) { pthread_cleanup_push(cleanup, NULL); // thread cleanup handler p thread_mutex_lock(&mutex); pthread_cond_wait(&cond, &mutex); pthread_mutex_unlock(&mutex); pthread_cleanup_pop(0); pthread_exit(NULL); }
參考博文:
取消線程及清理工作
http://blog.sina.com.cn/s/blog_66fb0c830100y9hb.html
(線程終止的清理工作)
http://www.cnblogs.com/mydomain/archive/2011/08/15/2139826.html
(一個 pthread_cancel 引起的線程死鎖)
http://www.cnblogs.com/mydomain/archive/2011/08/15/2139830.html
http://www.cnblogs.com/mydomain/archive/2011/08/15/2139850.html
自己sina博文(JAVA多線程相關介紹)
http://blog.sina.com.cn/s/blog_8da6362401013rcl.html
https://www.cnblogs.com/lijunamneg/archive/2013/01/25/2877211.html
線程取消(pthread_cancel)