天天看點

99-再議 pthread_cancel1. 實驗1.2 運作結果及分析1.3 取消點2. 預設取消和異步取消3. 禁止取消4. 總結

線上程建立與終止中就曾提到過 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();
  for (i = ; i < ; ++i) {
    for (j = ; j < ; ++j);
  }   
  printf("finished, consume %ld s\n", time() - 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 ;
}
           

1.2 運作結果及分析

99-再議 pthread_cancel1. 實驗1.2 運作結果及分析1.3 取消點2. 預設取消和異步取消3. 禁止取消4. 總結

圖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();
  for (i = ; i < ; ++i) {
    for (j = ; j < ; ++j);
  }   
  printf("finished, consume %ld s\n", time() - start);
  printf("cannot be canceled\n");
  return NULL;
}

int main() {
  pthread_t tid;
  pthread_create(&tid, NULL, fun, NULL);
  sleep();
  pthread_cancel(tid);
  puts("cancel thread");
  pthread_join(tid, NULL);
  return ;
}
           

2.2 運作結果

99-再議 pthread_cancel1. 實驗1.2 運作結果及分析1.3 取消點2. 預設取消和異步取消3. 禁止取消4. 總結

圖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();
  for (i = ; i < ; ++i) {
    for (j = ; j < ; ++j);
  }   
  printf("finished, consume %ld s\n", time() - 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 ;
}
           

3.2 運作結果

99-再議 pthread_cancel1. 實驗1.2 運作結果及分析1.3 取消點2. 預設取消和異步取消3. 禁止取消4. 總結

圖3 将線程設定為禁用取消狀态

從圖 3 中可以看到,将線程設定為

PTHREAD_CANCEL_DISABLE

後,線程是無法被取消的。

4. 總結

  • 了解取消點的概念
  • 掌握異步取消
  • 掌握禁用取消

繼續閱讀