Linux執行緒退出方式總結
在編寫多執行緒程式碼時,經常面臨執行緒安全退出的問題。
一般情況下,選擇檢查標誌位的方式:
線上程的while迴圈中,執行完例程後,都對標誌位進行檢查,如果標誌位指示繼續執行則再次執行例程,如果標誌位設定為退出狀態,則跳出迴圈,結束執行緒的執行。
這個標誌位需要主執行緒(或其他執行緒)設定,設定後,主執行緒呼叫pthread_join介面進入休眠(介面引數指定了等待的執行緒控制指標),子執行緒退出後,主執行緒會接收到系統的訊號,從休眠中恢復,這個時候就可以去做相關的資源清除動作。
這個方法可以保證子執行緒完全退出,主執行緒再去做相關的資源清除操作
時序圖如下
但是某些應用中,或許會發生下面情況:
子執行緒阻塞在某個操作無法被喚醒,即使主執行緒設定了標誌位,由於子執行緒進入了休眠無法醒過來,也沒有辦法去檢查標誌位,這個時候呼叫pthread_join進入休眠的主執行緒等待不到子執行緒退出的訊號,也會一直休眠,系統進入死鎖。
為了更安全地使執行緒退出,主執行緒通過pthread_cancel函式來請求取消同一程序中的其他執行緒,再呼叫pthread_join等待指定執行緒退出。使用pthread_cancel介面,需要了解Linux下執行緒的兩個屬性,可取消狀態和可取消型別,以及取消點的概念。
可取消狀態:包括PTHREAD_CANCEL_ENABLE和PTHREAD_CANCEL_DISABLE。當執行緒處於PTHREAD_CANCEL_ENABLE,收到cancel請求會使該執行緒退出執行;反之,若處於PTHREAD_CANCEL_DISABLE,收到的cancel請求將處於未決狀態,執行緒不會退出。執行緒啟動時的預設可取消狀態為PTHREAD_CANCEL_ENABLE,可以通過介面pthread_setcancelstate改變可取消狀態的屬性。
可取消型別:包括PTHREAD_CANCEL_DEFERRED和PTHREAD_CANCEL_ASYNCHRONOUS。當處於PTHREAD_CANCEL_DEFERRED,執行緒在收到cancel請求後,需要執行到取消點才能退出執行;如果處於PTHREAD_CANCEL_ASYNCHRONOUS,可以在任意時間取消,只要收到cancel請求即可馬上退出。執行緒啟動時預設可取消型別為PTHREAD_CANCEL_DEFERRED,可通過pthread_setcanceltype修改可取消型別。
取消點:執行緒檢查是否被取消並按照請求進行動作的一個位置。
採用PTHREAD_CANCEL_DEFERRED取消方式是因為執行緒可能在獲取臨界資源後(如獲取鎖),未釋放資源前收到退出訊號,如果使用PTHREAD_CANCEL_ ASYNCHRONOUS的方式,無論執行緒執行到哪個位置,都會馬上退出,而佔有的資源卻得不到釋放。
採用PTHREAD_CANCEL_DEFERRED取消方式,執行緒需要執行到取消點才退出,而主執行緒在呼叫pthread_cancel後,不能馬上進行執行緒資源釋放,必須呼叫pthread_join進入休眠,直至等待指定執行緒退出。
使用PTHREAD_CANCEL_DEFERRED方式並不能完全避免這個問題,因為無法保證在獲取臨界資源後(比如lock操作)不會進行可以作為取消點的操作(如進行sleep),此時主執行緒如果對該執行緒傳送cancel訊號,執行緒將會在不釋放鎖的情況下直接結束執行,即還是會出現在釋放資源前執行緒就退出的問題。
為了避免上述情況,不僅需要設定可取消型別,還需要設定可取消狀態。將獲取臨界資源-釋放臨界資源之間的程式碼塊都設定成PTHREAD_CANCEL_DISABLE狀態,其餘的程式碼塊都設定成PTHREAD_CANCEL_ENABLE狀態,確保執行緒在安全的地方退出。如果在可以安全退出的程式碼塊不存在取消點系統呼叫,可以呼叫pthread_testcancel函式自己新增取消點。
虛擬碼描述如下:
void* subThread(void*)
{
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,&oldCancleState);
…;//不存在獲取臨界資源操作,可以安全退出的程式碼塊
pthread_testcancel();//如果可以安全退出的程式碼塊不存在取消點操作,可以自己新增pthread_testcancel呼叫,執行緒執行到這個呼叫就會退出
/*還有一種方法,在可以安全退出的程式碼塊,我們將執行緒的可取消型別設定成PTHREAD_CANCEL_ ASYNCHRONOUS,這樣即使沒有取消點也可以馬上退出*/
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,&oldCancleState);
/*存在獲取-釋放臨界資源操作,如果在lock和unlock之間的執行收到cancel訊號,且可取消狀態為enable,則鎖永遠無法被釋放*/
Lock();
…;
Unlock();
}
void* mainThread(void*)
{
pthread_cancel(subThread);//給subThread傳送退出訊號
pthread_join(subThread,null);//進入休眠,直到subThread退出成功
}
無論使用哪種方式,核心點就是要保證執行緒退出的時候不會獲取了某些臨界資源而無法釋放
POSIX.1定義的取消點見下:
注意:當主執行緒呼叫pthread_cancel介面後,只是將取消請求傳送給指定執行緒,
對介面的成功呼叫不能保證指定執行緒已經退出,需要呼叫pthread_join等待指定執行緒完全退出,再進行相關資源的釋放。