pthread_cancel 與解構子行為實驗

·

2 min read

Manual

pthread_cancel(3) - Linux manual page

根據 linux manual 所寫,pthread_cancel 基本上可以在一些可中斷的地方(POSIX 給了一個清單,大概是 IO 操作的時候)中斷 thread。

這時就好奇了:C++ 的 Destructor 會不會被觸發?這並沒有寫在文件上。

Code

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>

struct Obj {
    Obj() {
        puts("constructor");
    }

    ~Obj() {
        puts("destructor");
    }
};

void* try_thread(void*) {
    Obj obj;
    puts("try_thread: start");
    int i;
    for (i = 0; i < 100; ++i) {
        printf("try_thread: %d\n", i);
        sleep(1);
    }
    puts("try_thread: end");
    return 0;
}

int main() {
    pthread_t t;
    pthread_create(&t, NULL, try_thread, NULL);
    sleep(1);
    sleep(1);

    pthread_cancel(t);

    pthread_join(t, NULL);
    puts("joined");
    return 0;
}

Result

gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.2)

constructor
try_thread: start
try_thread: 0
try_thread: 1
destructor
joined

Apple clang version 15.0.0 (clang-1500.0.40.1)

constructor
try_thread: start
try_thread: 0
try_thread: 1
joined

glibc Code Tracing

從結果上來看,glibc 的 pthread_cancel implement 可以 trigger Destructor ?

pthread_cancel.c - nptl/pthread_cancel.c - Glibc source code (glibc-2.38.9000) - Bootlin

裡面的機制是註冊一個 signal handler,然後對要 cancel 的 thread 扔 signal ,讓特定 thread 觸發 signal handler 呼叫 __do_cancel

pthreadP.h - sysdeps/nptl/pthreadP.h - Glibc source code (glibc-2.38.9000) - Bootlin

/* Called when a thread reacts on a cancellation request.  */
void __do_cancel () {
  // ...
  __pthread_unwind ((__pthread_unwind_buf_t *)
    THREAD_GETMEM (self, cleanup_jmp_buf));
}

裡面呼叫 __pthread_unwind

unwind.c - nptl/unwind.c - Glibc source code (glibc-2.38.9000) - Bootlin

看起來裡面是扔 Exception?所以可以觸發 Destructor。

void __pthread_unwind (__pthread_unwind_buf_t *buf) {
  struct pthread_unwind_buf *ibuf = (struct pthread_unwind_buf *) buf;
  struct pthread *self = THREAD_SELF;

  /* This is not a catchable exception, so don't provide any details about
     the exception type.  We do need to initialize the field though.  */
  THREAD_SETMEM (self, exc.exception_class, 0);
  THREAD_SETMEM (self, exc.exception_cleanup, &unwind_cleanup);

  _Unwind_ForcedUnwind (&self->exc, unwind_stop, ibuf);
  /* NOTREACHED */

  /* We better do not get here.  */
  abort ();
}

Reference

pthread_cancel(3) - Linux manual page

Cancelling a thread using pthread_cancel : good practice or bad

pthread_cancel considered harmful