線上程建立與終止中就曾提到過 pthread_cancel 函數,它是用來終止指定的線程的,就好像線程自己調用了 pthread_exit(PTHREAD_CANCEL) 一樣。不過,在那個時候并未讨論調用 pthread_cancel 後線程什麼時候終止。
本文主要目的就是驗證 pthread_cancel 的退出時機。
下面的幾個程式,線上程中編寫了計算密集型程式(一個很大的循環),循環執行完畢後,列印該循環消耗的時間。接下來,又列印了一行。
如果 cannot be canceled 列印出來,表明線程并未被取消。
1. 實驗
1.1 代碼
// cancel.c
#include <stdio.h>
#include <pthread.h>
#include <time.h>
void* fun(void *arg) {
int i, j, start;
start = time(0);
for (i = 0; i < 1000; ++i) {
for (j = 0; j < 1000000; ++j);
}
printf("finished, consume %ld s\n", time(0) - start);
printf("cannot be canceled\n");
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, fun, NULL);
pthread_cancel(tid);
puts("cancel thread");
pthread_join(tid, NULL);
return 0;
}
1.2 運作結果及分析

圖1 實驗 1 結果
從圖 1 中可以看到,當主線程發出取消請求後,線程并未立即停止,而是一直等到列印了 finished, consume 4 s 才停止。
1.3 取消點
出現圖 1 中的結果,原因在于 pthread_cancel 在預設情況下,隻是通知線程,至于線程什麼時候會取消,隻有在遇到了取消點(把它想象成某個函數)的時候才會停止。在密集計算的循環中,并沒有調用任何取消點函數。
當執行第一個 printf 函數時,線程退出了,說明在 printf 的調用鍊中,肯定有一個步驟調用了取消點函數。實際上,printf 在底層是調用了 write 接口,write 函數中存在取消點。
有很多函數都有取消點,具體可參考 apue 一書中的第 12 章——線程控制。
如果一直沒有機會調用包含取消點的函數,那就意味着線程即使收到了“取消信号”也不會退出。這時候可以顯示的線上程内部調用
void pthread_testcancel(void)
進行測試,如果真的收到了“取消信号”,則退出。
2. 預設取消和異步取消
有一種辦法,可以讓線程還沒到達取消點的時候直接退出。這時候需要将線程設定為異步取消的方式。具體方法為線上程内部調用下面的函數:
int pthread_setcanceltype(int type, int *oldtype);
其中,type 的值如下:
-
PTHREAD_CANCEL_ASYNCHRONOUS
-
PTHREAD_CANCEL_DEFERRED
預設情況下,線程取消方式為預設值——
PTHREAD_CANCEL_DEFERRED
。要想讓線程收到“取消信号”後立即退出,需要将 type 設定為
PTHREAD_CANCEL_ASYNCHRONOUS
.
2.1 代碼
// asyncancel.c
#include <stdio.h>
#include <pthread.h>
#include <time.h>
void* fun(void *arg) {
int i, j, start, oldtype;
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype);
start = time(0);
for (i = 0; i < 1000; ++i) {
for (j = 0; j < 1000000; ++j);
}
printf("finished, consume %ld s\n", time(0) - start);
printf("cannot be canceled\n");
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, fun, NULL);
sleep(1);
pthread_cancel(tid);
puts("cancel thread");
pthread_join(tid, NULL);
return 0;
}
2.2 運作結果
圖2 異步取消
從圖 2 可以看到,printf 函數還未執行,或者說還沒遇到取消點,線程就退出了。
3. 禁止取消
有些線程不是你想取消就取消的。就算你發送了 pthread_cancel 通知了,它也不會退出。除非它自己想退出了。
這時候,可以線上程函數中禁止取消。下面的函數可以實作此功能:
int pthread_setcancelstate(int state, int *oldstate);
其中,state 可以為下面的值:
-
PTHREAD_CANCEL_ENABLE
-
PTHREAD_CANCEL_DISABLE
預設情況下,state 的值為
PTHREAD_CANCEL_ENABLE
,即可以被取消。如果将 state 設定為
PTHREAD_CANCEL_DISABLE
,則線程是無法被取消的。
3.1 代碼
// disablecancel.c
#include <stdio.h>
#include <pthread.h>
#include <time.h>
void* fun(void *arg) {
int i, j, start, oldstate;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
start = time(0);
for (i = 0; i < 1000; ++i) {
for (j = 0; j < 1000000; ++j);
}
printf("finished, consume %ld s\n", time(0) - start);
printf("cannot be canceled\n");
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, fun, NULL);
pthread_cancel(tid);
puts("cancel thread");
pthread_join(tid, NULL);
return 0;
}
3.2 運作結果
圖3 将線程設定為禁用取消狀态
4. 總結
- 了解取消點的概念
- 掌握異步取消
- 掌握禁用取消