pthread_testcancel()32.4a — The Problem: Compute-Bound Loops
Section 32.3 showed that deferred cancellation only fires when a thread reaches a cancellation point — a function like sleep(), read(), or pthread_cond_wait().
But what about a thread doing pure computation — crunching numbers, sorting data, encrypting a file, rendering pixels — with no I/O and no blocking calls? Such a thread may never call any function from the cancellation point list. The cancellation request stays pending forever, and the thread never stops.
static void *
computeThread(void *arg)
{
long long result = 0;
/* Pure compute loop — NO cancellation points inside */
for (long long i = 0; i < 10000000000LL; i++) {
result += i * i; /* arithmetic only */
}
/* pthread_cancel() has no effect — this loop runs to completion
regardless of how many times you call pthread_cancel() */
return (void *)result;
}
pthread_cancel() immediately, this thread will run all 10 billion iterations before the pending request can ever fire. The cancel is completely stuck.pthread_testcancel() exists for exactly this purpose. Call it periodically inside any compute-bound loop to create a safe place where a pending cancellation can fire.32.4b — pthread_testcancel()
How It Works — Three Possible Outcomes
pthread_testcancel() checks for a pending cancellation, finds none, and returns immediately. No effect on the thread.Call Frequency — How Often to Call It?
There’s a balance to strike:
| Strategy | Pros | Cons |
|---|---|---|
| Every iteration | Fastest response to cancel | Function call overhead — slows tight loops |
| Every N iterations (e.g., every 1000) | Good balance: low overhead, timely cancel | Cancel may be slightly delayed |
| Never (no testcancel) | Zero overhead | Cancel never fires — thread runs to completion |
pthread_testcancel() often enough that cancellation responds within a reasonable time for your application — typically every few thousand iterations for heavy computation, or at natural “batch boundaries” (e.g., after processing each record).32.4c — Where to Place pthread_testcancel()
Place pthread_testcancel() at safe points in your loop — where the thread is in a consistent state (no half-completed operations, no held locks). If a cleanup handler must unlock a mutex, ensure the mutex is either held or not held — never mid-acquisition.
pthread_mutex_lock(&mtx);
pthread_testcancel(); /* BAD: mutex held! */
/* If canceled here, mutex stays locked */
do_work();
pthread_mutex_unlock(&mtx);
pthread_mutex_lock(&mtx);
do_work();
pthread_mutex_unlock(&mtx);
pthread_testcancel(); /* GOOD: no lock held */
/* Safe state: cleanup has nothing to undo */
Code Example 1: pthread_testcancel() in a Compute Loop
Sum-of-squares computation thread. Without testcancel(), the cancel would never fire. With it, the thread responds promptly.
/* testcancel_compute.c — pthread_testcancel() in a compute loop */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
static void *
computeThread(void *arg)
{
long long result = 0;
long long i;
printf("Compute: started (10 billion iterations)\n");
for (i = 0; i < 10000000000LL; i++) {
result += i * i;
/* Check for cancellation every 1 million iterations
— low overhead, yet responds within ~10ms */
if (i % 1000000 == 0) {
pthread_testcancel(); /* Acts as cancellation point */
}
}
printf("Compute: finished, result=%lld\n", result);
return (void *)result;
}
int
main(void)
{
pthread_t tid;
void *res;
pthread_create(&tid, NULL, computeThread, NULL);
sleep(1); /* Let thread start and run a bit */
printf("Main: canceling compute thread\n");
pthread_cancel(tid);
pthread_join(tid, &res);
if (res == PTHREAD_CANCELED)
printf("Main: compute thread was canceled successfully\n");
else
printf("Main: compute thread finished normally\n");
return 0;
}
pthread_testcancel() call inside the loop, the cancel would be pending but the thread would run all 10 billion iterations regardless. With it, the thread terminates at the next test point after the cancel request arrives.Code Example 2: Batch Processing with Periodic Cancellation Check
Processing records from an array. testcancel() called at batch boundaries — a more realistic pattern for data-processing threads.
/* batch_testcancel.c — testcancel at batch boundaries */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#define TOTAL_RECORDS 50
#define BATCH_SIZE 10
static void
process_record(int rec_num)
{
/* Simulate processing without any blocking/cancellation point */
volatile long sum = 0;
for (long j = 0; j < 500000; j++) sum += j;
(void)sum;
}
static void *
processorThread(void *arg)
{
int i, processed = 0;
printf("Processor: starting %d records in batches of %d\n",
TOTAL_RECORDS, BATCH_SIZE);
for (i = 0; i < TOTAL_RECORDS; i++) {
process_record(i);
processed++;
/* After every complete batch, check for cancellation */
if (processed % BATCH_SIZE == 0) {
printf("Processor: batch %d/%d done, checking cancel...\n",
processed / BATCH_SIZE, TOTAL_RECORDS / BATCH_SIZE);
pthread_testcancel(); /* Cancellation point between batches */
}
}
printf("Processor: all %d records processed\n", processed);
return (void *)(intptr_t)processed;
}
int
main(void)
{
pthread_t tid;
void *res;
pthread_create(&tid, NULL, processorThread, NULL);
/* Cancel after a short delay — thread should be mid-processing */
usleep(80000); /* 80ms */
printf("Main: sending cancel request\n");
pthread_cancel(tid);
pthread_join(tid, &res);
if (res == PTHREAD_CANCELED)
printf("Main: processor was canceled mid-batch\n");
else
printf("Main: processor completed all %ld records\n", (long)res);
return 0;
}
pthread_testcancel() between batches guarantees that individual records are always processed completely. You never cancel half-way through a record update, which could leave data in an inconsistent state.Code Example 3: Side-by-Side — With and Without testcancel()
Two threads run simultaneously. Thread A has testcancel(), Thread B does not. Only Thread A responds to cancel.
/* compare_testcancel.c — With vs without testcancel */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
/* Thread A: HAS pthread_testcancel() — responds to cancel */
static void *
threadA(void *arg)
{
for (long long i = 0; ; i++) {
if (i % 500000 == 0)
pthread_testcancel(); /* Has cancel point */
}
return NULL;
}
/* Thread B: NO pthread_testcancel() — cancel stays pending forever */
static void *
threadB(void *arg)
{
for (long long i = 0; i < 3000000000LL; i++) {
/* No testcancel — cancel pending but never fires */
(void)i;
}
printf("ThreadB: finished all iterations (cancel was ignored!)\n");
return NULL;
}
int
main(void)
{
pthread_t tidA, tidB;
void *resA, *resB;
pthread_create(&tidA, NULL, threadA, NULL);
pthread_create(&tidB, NULL, threadB, NULL);
sleep(1);
printf("Main: canceling both threads\n");
pthread_cancel(tidA);
pthread_cancel(tidB);
pthread_join(tidA, &resA);
printf("ThreadA: %s\n", resA == PTHREAD_CANCELED ?
"CANCELED (testcancel worked)" : "finished normally");
pthread_join(tidB, &resB);
printf("ThreadB: %s\n", resB == PTHREAD_CANCELED ?
"CANCELED" : "finished normally (cancel was ignored)");
return 0;
}
Interview Questions — Section 32.4
void pthread_testcancel(void); — takes no parameters and returns nothing (void). If cancellation is pending and enabled, it does not return at all — the thread terminates.pthread_testcancel() gives you control over where cancellation can occur, making cleanup handlers reliable.pthread_testcancel() is explicitly listed in SUSv3’s Table of required cancellation points. It is the only function whose sole purpose is to be a cancellation point — all other functions in the list have primary non-cancellation purposes.