在通常情况下,程序中的多个线程并发执行,每个线程各司其职,直至其决意退出,随即会调用函数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的调用执行。