關於pthread_cancel
軟件版本:
操作系統:ubuntu10.04
內核版本:Linux version 2.6.32-36-generic
目錄:
1. 線程終止方式
2. pthread_cancel 請求退出
3. 由 pthread_cancel 引起的死鎖問題
4. 關於 pthread_cancel 取消點
5. 參考資料
1. 線程終止方式
線程可能的終止方式包括:
· return 從啟動例程中返回,返回值就是線程的退出碼。進程中的其它線程可通過 pthread_join 函數獲取這個返回值。
· void pthread_exit(void *rval_ptr); 退出線程,進程中的其它線程可通過 pthread_join 函數訪問到 rval_ptr 這個指針。
· exit 、_Exit 或 _exit 。進程中的任一線程調用該函數,則終止整個進程。請慎用。
· int pthread_cancel(pthread_t tid); 請求同一進程中的其它線程退出。要注意的是,該函數並不等待線程終止,他僅僅提出請求。調用了該函數也不等於目標線程馬上就會退出,目標線程有可能再運行一段時間后到達取消點才退出;甚至有可能不響應退出。
· int pthread_join(pthread_tthread, void **rval_ptr); 調用線程將一直阻塞,直到指定線程退出。如果目標線程處於分離狀態時,pthread_join 馬上返回 EINVAL 。
本文的主要目的想介紹一下關於 pthread_cancel 的一些需要注意的地方。
2. pthread_cancel 請求退出
基本用法:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void* thread(void* data)
{
printf("thread start......\n");
while(1)
{
printf("thread running...\n");
sleep(1);
}
printf("thread exit...\n");
pthread_exit(NULL);
}
int main()
{
pthread_t tid;
if (pthread_create(&tid, NULL, thread, NULL) != 0)
{
exit(1);
}
sleep(5); // sleep a while.
pthread_cancel(tid);
pthread_join(tid, NULL);
printf("main thread exit...\n");
return 0;
}
運行后的打印:
$./main
thread start......
thread running...
thread running...
thread running...
thread running...
thread running...
main thread exit...
新建的線程如我們所願的退出了,但是從打印來看,thread 並沒有執行最后的 printf 函數,也就是說線程非正常退出。那么如果在線程剛開始的時候申請了一些系統資源,該如何釋放呢?這時就需要用到線程清理處理程序。
#include <pthread.h>
void pthread_cleanup_push(void (*rtn)(void *), void *arg);
void pthread_cleanup_pop(int execute);
關於這對函數,要注意的是他們必須成對出現。否則就會編譯出錯,而且錯誤比較奇怪。這是由於他的定義所造成的,喜歡刨根問底的可以去看一下 pthread.h 這個頭文件中關於該函數的定義,這里我就不再展開了。
運用 cleanup 釋放資源的 demo 。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
void cleanup_function(void *data)
{
printf("run thread cleanup function.\n");
}
void* thread(void* arg)
{
printf("thread start......\n");
pthread_cleanup_push(cleanup_function, NULL);
while(1)
{
printf("thread running...\n");
sleep(1);
}
printf("thread exit...\n");
pthread_cleanup_pop(0);
pthread_exit(NULL);
}
int main(int argc, char **argv)
{
pthread_t tid;
if (pthread_create(&tid, NULL, thread, NULL) != 0)
{
exit(1);
}
sleep(5); // sleep a while.
pthread_cancel(tid);
pthread_join(tid, NULL);
printf("main thread exit...\n");
return 0;
}
將上面的代碼編譯運行后,打印為:
$./main
thread start......
thread running...
thread running...
thread running...
thread running...
thread running...
run thread cleanup function.
main thread exit...
從打印可以看出,thread 在退出前調用了 cleanup 函數。
3. 由 pthread_cancel 引起的死鎖問題
程序在運行的過程中突然間被取消掉,可能引發的問題有許多,其中之一就是死鎖的問題。下面這段代碼就是一個引起死鎖的例子[2]。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void* thread_0(void* data)
{
pthread_mutex_lock(&mutex);
printf("thread_0: 1\n");
pthread_cond_wait(&cond, &mutex);
printf("thread_0: 2\n");
pthread_mutex_unlock(&mutex);
printf("thread_0: 3\n");
pthread_exit(NULL);
}
void* thread_1(void* data)
{
sleep(5);
printf("thread_1: 1\n");
pthread_mutex_lock(&mutex);
printf("thread_1: 2\n");
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
printf("thread_1: 3\n");
pthread_exit(NULL);
}
int main()
{
pthread_t tid[2];
if (pthread_create(&tid[0], NULL, thread_0, NULL) != 0)
{
exit(1);
}
if (pthread_create(&tid[1], NULL, thread_1, NULL) != 0)
{
exit(1);
}
sleep(2);
printf("main: 1\n");
pthread_cancel(tid[0]);
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
printf("main: return \n");
return 0;
}
該程序運行時會產生死鎖,產生死鎖的原因就是因為線程0被取消后,沒有執行 pthread_mutex_unlock(&mutex); 。導致線程1一直獲取不到 mutex 而產生死鎖現象。
這個例子提出了一個值得我們思考的問題,就是到底線程什么時候會響應 pthread_cancel 的請求而退出?下面來介紹一下取消選項。
4. 關於 pthread_cancel 取消點
首先我們要清楚明白的是 pthread_cancel 只是提出取消請求。至於如何處理這個 cancel 信號則由線程自己決定,可以響應取消,也可以不響應取消;可以馬上響應,也可以延時處理。
在 pthread_attr_t 線程屬性結構中,有兩個線程屬性沒有被包含其中,他們分別是可取消狀態(PTHREAD_CANCEL_ENABLE/PTHREAD_CANCEL_DISABLE)和可取消類型(PTHREAD_CANCEL_DEFERRED/PTHREAD_CANCEL_ASYNCHRONOUS)。
線程啟動時的默認可取消狀態為 PTHREAD_CANCEL_ENABLE 。可通過調用函數 pthread_setcancelstate 修改。
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
線程啟動時的默認可取消類型為 PTHREAD_CANCEL_DEFERRED 。可通過調用函數 pthread_setcanceltype 修改。
#include <pthread.h>
int pthread_setcanceltype(int type, int *oldtype);
當線程的可取消狀態為 PTHREAD_CANCEL_ENABLE 且可取消類型為延時取消(PTHREAD_CANCEL_DEFERRED)時,線程在取消請求發出以后還是繼續運行,直到到達某個取消點。取消點就是線程檢查是否被取消並按照請求進行動作的一個位置。如果想了解取消點有哪些可以參考《UNIX 環境高級編程》第411頁表格12-7與12-8。由於函數非常多,我就不一一列出來了。其中死鎖例子中用到的 pthread_cond_wait 就是一個可取消點。
當線程的可取消狀態為 PTHREAD_CANCEL_ENABLE 且可取消類型為異步取消(PTHREAD_CANCEL_ASYNCHRONOUS)時,線程可以在任意時刻取消,而不是非得遇到取消點才能被取消。
當線程的可取消狀態為 PTHREAD_CANCEL_DISABLE 時,線程不響應 cancel 信號。
現在我們來將上一節中的死鎖例子的線程0的可取消狀態設置為 PTHREAD_CANCEL_DISABLE 。看會有什么樣的結果。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void* thread_0(void* data)
{
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
pthread_mutex_lock(&mutex);
printf("thread_0: 1\n");
pthread_cond_wait(&cond, &mutex);
printf("thread_0: 2\n");
pthread_mutex_unlock(&mutex);
printf("thread_0: 3\n");
pthread_exit(NULL);
}
void* thread_1(void* data)
{
sleep(5);
printf("thread_1: 1\n");
pthread_mutex_lock(&mutex);
printf("thread_1: 2\n");
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
printf("thread_1: 3\n");
pthread_exit(NULL);
}
int main()
{
pthread_t tid[2];
if (pthread_create(&tid[0], NULL, thread_0, NULL) != 0)
{
exit(1);
}
if (pthread_create(&tid[1], NULL, thread_1, NULL) != 0)
{
exit(1);
}
sleep(2);
printf("main: 1\n");
pthread_cancel(tid[0]);
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
printf("main: return \n");
return 0;
}
編譯運行后的打印:
$./main
thread_0: 1
main: 1
thread_1: 1
thread_1: 2
thread_1: 3
thread_0: 2
thread_0: 3
main: return
可以看到程序並沒有產生死鎖。其實就是由於線程0設置為不響應 cancel 信號,所以才能夠正常返回。
5. 參考資料
[1] 《UNIX 環境高級編程》
[2] http://www.cnblogs.com/mydomain/archive/2011/08/15/2139830.html
[3] http://www.cnblogs.com/mydomain/archive/2011/08/15/2139850.html