如何在C++11中实现类似Java的InterruptedException机制?
Great question! Linux's native pthread signal handling is definitely clunky compared to Java's clean interrupt model, where blocked threads get a nice InterruptedException when interrupted. Let's walk through a practical implementation that mimics this behavior—allowing you to interrupt threads waiting on mutexes, semaphores, or joins, with per-thread interrupt state that doesn't interfere with other running threads.
The approach centers around two key pieces:
- A thread-safe per-thread interrupt flag to track whether a thread has been marked for interruption.
- Wrapped versions of blocking synchronization calls that check this flag before blocking, and handle wake-ups from a dedicated interrupt signal to re-check the flag.
Unlike raw pthread signals (which are global and hard to scope), this model keeps interrupt state isolated to individual threads, just like Java's Thread.interrupt().
1. Thread Interrupt State Management
First, we need a way to track interrupt state per thread, and a safe way for other threads to trigger an interrupt. We'll use a thread-safe hash table to map pthread_t IDs to their interrupt flags, plus a dedicated signal (SIGUSR1) to wake blocked threads.
Setup Code
#include <pthread.h> #include <signal.h> #include <stdlib.h> #include <time.h> #include <errno.h> #include <stdbool.h> // Thread-safe hash table (simple mutex-protected structure for example) typedef struct { pthread_mutex_t mutex; pthread_t *tids; bool *flags; size_t count; size_t capacity; } InterruptTable; static InterruptTable g_interrupt_table; static pthread_key_t t_interrupt_key; // For current thread to access its own flag // Initialize the interrupt system void interrupt_init(void) { pthread_mutex_init(&g_interrupt_table.mutex, NULL); g_interrupt_table.tids = malloc(8 * sizeof(pthread_t)); g_interrupt_table.flags = malloc(8 * sizeof(bool)); g_interrupt_table.capacity = 8; g_interrupt_table.count = 0; pthread_key_create(&t_interrupt_key, NULL); } // Cleanup the interrupt system void interrupt_cleanup(void) { pthread_mutex_lock(&g_interrupt_table.mutex); free(g_interrupt_table.tids); free(g_interrupt_table.flags); pthread_mutex_unlock(&g_interrupt_table.mutex); pthread_mutex_destroy(&g_interrupt_table.mutex); pthread_key_delete(t_interrupt_key); } // Register the current thread for interrupt support void thread_register_for_interrupt(void) { pthread_mutex_lock(&g_interrupt_table.mutex); // Check if already registered for (size_t i = 0; i < g_interrupt_table.count; i++) { if (pthread_equal(g_interrupt_table.tids[i], pthread_self())) { pthread_setspecific(t_interrupt_key, &g_interrupt_table.flags[i]); pthread_mutex_unlock(&g_interrupt_table.mutex); return; } } // Resize table if needed if (g_interrupt_table.count == g_interrupt_table.capacity) { g_interrupt_table.capacity *= 2; g_interrupt_table.tids = realloc(g_interrupt_table.tids, g_interrupt_table.capacity * sizeof(pthread_t)); g_interrupt_table.flags = realloc(g_interrupt_table.flags, g_interrupt_table.capacity * sizeof(bool)); } // Add current thread to the table g_interrupt_table.tids[g_interrupt_table.count] = pthread_self(); g_interrupt_table.flags[g_interrupt_table.count] = false; pthread_setspecific(t_interrupt_key, &g_interrupt_table.flags[g_interrupt_table.count]); g_interrupt_table.count++; pthread_mutex_unlock(&g_interrupt_table.mutex); } // Interrupt a target thread void thread_interrupt(pthread_t tid) { pthread_mutex_lock(&g_interrupt_table.mutex); for (size_t i = 0; i < g_interrupt_table.count; i++) { if (pthread_equal(g_interrupt_table.tids[i], tid)) { g_interrupt_table.flags[i] = true; pthread_kill(tid, SIGUSR1); // Wake up any blocked system calls break; } } pthread_mutex_unlock(&g_interrupt_table.mutex); } // Check if current thread is interrupted bool thread_is_interrupted(void) { bool *flag = pthread_getspecific(t_interrupt_key); return flag ? *flag : false; } // Reset interrupt state for current thread void thread_reset_interrupt(void) { bool *flag = pthread_getspecific(t_interrupt_key); if (flag) *flag = false; }
Signal Handler
We just need a minimal handler to wake blocked calls—no heavy logic here:
void interrupt_signal_handler(int sig) { // Do nothing except wake up blocked system calls like sigsuspend() }
2. Wrapping Blocking Synchronization Calls
Now we wrap all the blocking operations you care about (mutex lock, semaphore wait, pthread join) to check for interrupts before and during blocking.
Interruptible Mutex Lock
int pthread_mutex_lock_interruptible(pthread_mutex_t *mutex) { while (1) { // Check if interrupted before attempting to lock if (thread_is_interrupted()) { return -EINTR; // Custom error to signal interrupt } // Try to lock without blocking int ret = pthread_mutex_trylock(mutex); if (ret == 0) { return 0; // Lock acquired successfully } else if (ret != EBUSY) { return ret; // Actual error, not just busy } // Prepare to wait for interrupt or mutex availability struct sigaction sa, old_sa; sa.sa_handler = interrupt_signal_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGUSR1, &sa, &old_sa); // Wait for SIGUSR1 to wake us up sigset_t wait_mask; sigemptyset(&wait_mask); sigsuspend(&wait_mask); // Restore original signal handler sigaction(SIGUSR1, &old_sa, NULL); } }
Interruptible Semaphore Wait
int sem_wait_interruptible(sem_t *sem) { while (1) { if (thread_is_interrupted()) { return -EINTR; } int ret = sem_trywait(sem); if (ret == 0) { return 0; } else if (ret != EAGAIN) { return ret; } // Same signal handling as mutex lock struct sigaction sa, old_sa; sa.sa_handler = interrupt_signal_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGUSR1, &sa, &old_sa); sigset_t wait_mask; sigemptyset(&wait_mask); sigsuspend(&wait_mask); sigaction(SIGUSR1, &old_sa, NULL); } }
Interruptible Thread Join
For joins, we use pthread_timedjoin_np to periodically check for interrupts:
int pthread_join_interruptible(pthread_t thread, void **retval) { struct timespec ts; while (1) { if (thread_is_interrupted()) { return -EINTR; } // Wait for 1 second, then re-check interrupt state clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += 1; int ret = pthread_timedjoin_np(thread, retval, &ts); if (ret == 0) { return 0; // Thread finished successfully } else if (ret != ETIMEDOUT) { return ret; // Actual error } } }
3. Using the Interrupt System
In your threads, you just need to:
- Register the thread for interrupts with
thread_register_for_interrupt()at startup. - Use the wrapped interruptible functions instead of raw pthread/sem calls.
- Check for
-EINTRreturn codes to handle interrupts (in C++ you could throw a customInterruptedExceptionhere).
Example Thread Code
void *worker_thread(void *arg) { thread_register_for_interrupt(); pthread_mutex_t *mutex = (pthread_mutex_t*)arg; while (1) { int ret = pthread_mutex_lock_interruptible(mutex); if (ret == -EINTR) { // Handle interrupt: clean up and exit printf("Thread %lu was interrupted, exiting\n", (unsigned long)pthread_self()); return NULL; } else if (ret != 0) { // Handle actual mutex error perror("pthread_mutex_lock_interruptible"); return NULL; } // Do work with the mutex held printf("Thread %lu acquired mutex\n", (unsigned long)pthread_self()); sleep(1); pthread_mutex_unlock(mutex); } }
Triggering an Interrupt
From another thread, just call thread_interrupt(target_tid):
pthread_t worker; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_create(&worker, NULL, worker_thread, &mutex); // Later, interrupt the worker sleep(3); thread_interrupt(worker); pthread_join(worker, NULL);
Key Notes
- Thread Safety: The interrupt table is protected by a mutex, so multiple threads can trigger interrupts safely.
- No Cross-Thread Interference: Each thread's interrupt flag is independent—interrupting one thread doesn't affect others.
- Signal Safety: The signal handler is minimal (does nothing), so it's safe to use with system calls.
- Cleanup: Don't forget to call
interrupt_cleanup()when your program exits to free resources.
内容的提问来源于stack exchange,提问作者peterk

