6 - Deferred Thread Cancellation
Program
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/**
* Following is same as the last example as before but thread cancellation is performed in a Deferred Way. This way, we
* can define points in the code where any pending thread cancellation request is checked for.
*
* It is there, the cleanup handlers are invoked and the cancellation sequence starts. We define these check points
* using the `pthread_testcancel()` function. There are other cancellation points other than this, like `sleep()`,
* `read()`, `write()`, etc.
*/
#define WORKER_THREADS 5
pthread_t threads[WORKER_THREADS];
void cleanup_thread_arg(void* arg) {
printf("Invoked cleanup_thread_arg");
free(arg);
}
void close_thread_file(void* arg) {
printf("Invoked close_thread_file");
fclose(arg);
}
void* write_to_file(void* th_id) {
char file_name[64];
char file_contents[128];
const char thread_id = *(char*)th_id;
int counter = 0;
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
pthread_cleanup_push(cleanup_thread_arg, th_id);
sprintf(file_name, "./files/thread_%d.txt", thread_id);
FILE* file = fopen(file_name, "w");
pthread_cleanup_push(close_thread_file, file);
if (file == NULL) {
perror("Failed to open the file");
exit(-1);
}
while (counter < 50) {
const int file_content_len = sprintf(file_contents, "%d: This is thread %d\n", counter, thread_id);
fwrite(file_contents, sizeof(char), file_content_len, file);
fflush(file);
counter++;
sleep(1);
pthread_testcancel();
}
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
return NULL;
}
void cancel_thread(const char thread_id) {
const char thread_cancel_status = pthread_cancel(threads[thread_id]);
if (thread_cancel_status) {
printf("Failed to cancel thread: %d", thread_id);
}
}
void menu() {
short int choice = -1;
while (1) {
printf("Enter the thread id to cancel [0-%d]: ", WORKER_THREADS - 1);
scanf("%hd", &choice);
if (choice < 0 || choice > WORKER_THREADS - 1) {
printf("Incorrect thread id %d.\n", choice);
exit(0);
}
cancel_thread(choice);
}
}
int main() {
pthread_attr_t thread_attr;
pthread_attr_init(&thread_attr);
pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
for (int i = 0; i < WORKER_THREADS; i++) {
char* thread_arg = calloc(1, sizeof(char));
*thread_arg = i;
const char thread_create_status = pthread_create(&threads[i], &thread_attr, write_to_file, thread_arg);
if (thread_create_status) {
printf("Failed to create thread %d\n", i);
exit(-1);
}
}
menu();
return 0;
}Deferred Cancellation
With deferred cancellation, a cancellation request is not immediately acted upon.
Instead:
- Another thread calls
pthread_cancel(thread_id);- The target thread receives a pending cancellation request.
- The thread only terminates when it reaches a cancellation point.
Cancellation Points
A cancellation point is a location where the thread checks whether a cancellation request is pending.
Examples include:
sleep()read()write()pthread_testcancel()
This program explicitly defines a cancellation point:
pthread_testcancel();inside the worker loop.
Flow inside the loop:
write to file
flush buffer
sleep
check for cancellationIf a cancellation request exists at pthread_testcancel():
- Cleanup handlers are invoked
- The thread terminates
Cleanup Handlers
Deferred cancellation works safely because cleanup handlers guarantee that resources are released before the thread exits.
Each thread maintains a cleanup handler stack.
Handlers are pushed using:
pthread_cleanup_push(handler, arg);and popped using:
pthread_cleanup_pop(execute);Where:
execute = 0→ remove handler onlyexecute = 1→ execute handler and remove it
Cleanup Handlers in This Program
1. Freeing Thread Argument
Each thread receives dynamically allocated memory for its ID.
Cleanup handler:
void cleanup_thread_arg(void* arg) {
free(arg);
}Registered using:
pthread_cleanup_push(cleanup_thread_arg, th_id);2. Closing the File
Each thread writes to its own file.
Cleanup handler:
void close_thread_file(void* arg) {
fclose(arg);
}Registered using:
pthread_cleanup_push(close_thread_file, file);Order of Cleanup Execution
Cleanup handlers behave like a stack (LIFO).
Push order:
cleanup_thread_arg
close_thread_fileExecution order during cancellation:
close_thread_file
cleanup_thread_argThis ensures resources are released in the reverse order of acquisition.