在通常情况下,程序中的多个线程并发执行,每个线程各司其职,直至其决意退出,随即会调用函数pthread_exit()或者从线程启动函数中返回。
有时候,需要将一个线程取消,亦即向线程发送一个请求,要求其立即退出。比如一组线程正在执行一个运算,一旦某个线程检测到错误发生,需要其他线程退出,取消线程的功能这时就能派上用场。
另一种情况,一个图形用户界面(GUI)驱动的应用程序可能会提供一个“取消”按钮,以便用户可以终止后台某一线程正在执行的任务。这种情况下,主线程(控制图形用户界面)需要请求后台线程退出。
本文记录学习TLPI一书第32章线程:线程取消内容,讨论POSIX线程的取消机制。

/*************************************************************************\
*                  Copyright (C) Michael Kerrisk, 2018.                   *
*                                                                         *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU General Public License as published by the   *
* Free Software Foundation, either version 3 or (at your option) any      *
* later version. This program is distributed without any warranty.  See   *
* the file COPYING.gpl-v3 for details.                                    *
\*************************************************************************/

/* Listing 32-1 */

/* thread_cancel.c

   Demonstrate the use of pthread_cancel() to cancel a POSIX thread.
*/
#include <pthread.h>
#include "tlpi_hdr.h"

static void *
threadFunc(void *arg)
{
    int j;

    printf("New thread started\n");     /* May be a cancellation point */
    for (j = 1; ; j++) {
        printf("Loop %d\n", j);         /* May be a cancellation point */
        sleep(1);                       /* A cancellation point */
    }

    /* NOTREACHED */
    return NULL;
}

int
main(int argc, char *argv[])
{
    pthread_t thr;
    int s;
    void *res;

    s = pthread_create(&thr, NULL, threadFunc, NULL);
    if (s != 0)
        errExitEN(s, "pthread_create");

    sleep(3);                           /* Allow new thread to run a while */

    s = pthread_cancel(thr);
    if (s != 0)
        errExitEN(s, "pthread_cancel");

    s = pthread_join(thr, &res);
    if (s != 0)
        errExitEN(s, "pthread_join");

    if (res == PTHREAD_CANCELED)
        printf("Thread was canceled\n");
    else
        printf("Thread was not canceled (should not happen!)\n");

    exit(EXIT_SUCCESS);
}

运行结果:

[root threads]#./thread_cancel
New thread started
Loop 1
Loop 2
Loop 3
Thread was canceled

上面的程序清单是一个使用pthread_cancel()的简单例子,主程序创建一个线程来执行无限循环,每次都在休眠一秒后打印循环计数器的值。仅当向其发送取消请求或者进程退出时,该线程才会终止。主程序休眠3秒,随机向新创建的线程发送取消请求。
上面由main创建的线程会执行到属于取消点的函数(sleep()属于取消点,printf可能也是),因此会接受取消请求,假设线程执行的是一个不含取消点的循环,这时线程永远也不会响应取消请求。
函数pthread_testcancel()目的很简单,就是生成一个取消点。线程如果已有处于挂起状态的取消请求,那么只要调用该函数,线程就会随之终止。
当线程执行的代码未包含取消点时,可以周期性地调用pthread_testcancel(),以确保对其他线程向其发送的取消请求做出及时响应。

清理函数

每个线程都可以拥有一个清理函数栈,当线程遭取消时,会沿该栈自顶向下依次执行清理函数,首先会执行最近设置的函数,接着是次新的函数,依次类推。当执行完所有清理函数后,线程终止。
函数pthread_cleanup_push()和pthread_clean_pop()分别负责向调用线程的清理函数栈添加和移除清理函数。

/*************************************************************************\
*                  Copyright (C) Michael Kerrisk, 2018.                   *
*                                                                         *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU General Public License as published by the   *
* Free Software Foundation, either version 3 or (at your option) any      *
* later version. This program is distributed without any warranty.  See   *
* the file COPYING.gpl-v3 for details.                                    *
\*************************************************************************/

/* Listing 32-2 */

/* thread_cleanup.c

   An example of thread cancellation using the POSIX threads API:
   demonstrate the use of pthread_cancel() and cleanup handlers.
*/
#include <pthread.h>
#include "tlpi_hdr.h"

static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static int glob = 0;                    /* Predicate variable */

static void     /* Free memory pointed to by 'arg' and unlock mutex */
cleanupHandler(void *arg)
{
    int s;

    printf("cleanup: freeing block at %p\n", arg);
    free(arg);

    printf("cleanup: unlocking mutex\n");
    s = pthread_mutex_unlock(&mtx);
    if (s != 0)
        errExitEN(s, "pthread_mutex_unlock");
}

static void *
threadFunc(void *arg)
{
    int s;
    void *buf = NULL;                   /* Buffer allocated by thread */

    buf = malloc(0x10000);              /* Not a cancellation point */
    printf("thread:  allocated memory at %p\n", buf);

    s = pthread_mutex_lock(&mtx);       /* Not a cancellation point */
    if (s != 0)
        errExitEN(s, "pthread_mutex_lock");

    pthread_cleanup_push(cleanupHandler, buf);

    while (glob == 0) {
        s = pthread_cond_wait(&cond, &mtx);     /* A cancellation point */
        if (s != 0)
            errExitEN(s, "pthread_cond_wait");
    }

    printf("thread:  condition wait loop completed\n");
    pthread_cleanup_pop(1);             /* Executes cleanup handler */
    return NULL;
}

int
main(int argc, char *argv[])
{
    pthread_t thr;
    void *res;
    int s;

    s = pthread_create(&thr, NULL, threadFunc, NULL);
    if (s != 0)
        errExitEN(s, "pthread_create");

    sleep(2);                   /* Give thread a chance to get started */

    if (argc == 1) {            /* Cancel thread */
        printf("main:    about to cancel thread\n");
        s = pthread_cancel(thr);
        if (s != 0)
            errExitEN(s, "pthread_cancel");

    } else {                    /* Signal condition variable */
        printf("main:    about to signal condition variable\n");

        s = pthread_mutex_lock(&mtx);   /* See the TLPI page 679 erratum */
        if (s != 0)
            errExitEN(s, "pthread_mutex_lock");

        glob = 1;

        s = pthread_mutex_unlock(&mtx); /* See the TLPI page 679 erratum */
        if (s != 0)
            errExitEN(s, "pthread_mutex_unlock");

        s = pthread_cond_signal(&cond);
        if (s != 0)
            errExitEN(s, "pthread_cond_signal");
    }

    s = pthread_join(thr, &res);
    if (s != 0)
        errExitEN(s, "pthread_join");
    if (res == PTHREAD_CANCELED)
        printf("main:    thread was canceled\n");
    else
        printf("main:    thread terminated normally\n");

    exit(EXIT_SUCCESS);
}

执行结果:

[root threads]#./thread_cleanup
thread:  allocated memory at 0x7fbb94000b20
main:    about to cancel thread
cleanup: freeing block at 0x7fbb94000b20
cleanup: unlocking mutex
main:    thread was canceled
[root threads]#
[root threads]#
[root threads]#./thread_cleanup s
thread:  allocated memory at 0x7f2998000b20
main:    about to signal condition variable
thread:  condition wait loop completed
cleanup: freeing block at 0x7f2998000b20
cleanup: unlocking mutex
main:    thread terminated normally

不带任何命令行参数,那么main()函数会调用pthread_cancel(),清理函数也会得以自动执行。
如果运行该程序且带有命令行参数,那么main将glob设置为1并通知条件变量,清理函数则通过pthread_cleanup_pop的调用执行。