關於pthread_cancel


關於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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM