Mutexes and Interrupts

  Coding

In a multi-threaded program, locks and interrupts can seem like an irresistable force meeting an immovable object. After all, if a thread is blocked, can it handle an event? Well, it depends on how you set it up, but at least in C, you can force the issue, and one of them does in fact win. The interrupt. Even a fully mutex-locked thread can be made to handle an incoming interrupt. Here’s code proving it:

$ cat interrupt_block.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>

pthread_mutex_t lock;
pthread_t a, b, c;
int dummy;

void* fa( void* v );
void* fb( void* v );
void* fc( void* v );
void sig( int );

int main( int argc, char **argv )
{
 pthread_mutex_init( &lock, NULL );
 pthread_create( &a, NULL, fa, &dummy );
 pthread_create( &b, NULL, fb, &dummy );
 pthread_create( &c, NULL, fc, &dummy );
 pthread_exit(0);
} 

void* fa( void* v )
{
 sigset_t block, old;
 sigemptyset( &block );
 sigaddset( &block, SIGINT );
 pthread_sigmask( SIG_BLOCK, &block, &old ); /* block SIGINT */
 int p = (int)pthread_self();
 fprintf( stderr, "this is thread %d in fa(), about to grab lock...\n", (int)pthread_self() );
 pthread_mutex_lock( &lock );
 fprintf( stderr, "this is thread %d in fa(), got lock, sleeping for a long time...\n", (int)pthread_self() );
 sleep(99999);
 return NULL;
}

void* fb( void* v )
{
 sigset_t block, old;
 sigemptyset( &block );
 pthread_sigmask( SIG_BLOCK, &block, &old ); /* DON'T block SIGINT */
 fprintf( stderr, "this is thread %d in fb(), setting up as the only SIGINT handling thread...\n", (int)pthread_self() );
 signal( SIGINT, sig );
 fprintf( stderr, "this is thread %d in fb(), waiting a bit for fa() to grab lock first...\n", (int)pthread_self() );
 sleep(1); /* 1s should be enough for fa() to grab lock first */
 fprintf( stderr, "this is thread %d in fb(), about to grab lock and hang forever waiting...\n", (int)pthread_self() );
 pthread_mutex_lock( &lock );
 return NULL;
}

void* fc( void* v )
{
 sigset_t block, old;
 sigemptyset( &block );
 sigaddset( &block, SIGINT );
 pthread_sigmask( SIG_BLOCK, &block, &old ); /* block SIGINT */
 fprintf( stderr, "this is thread %d in fc(), waiting a bit for fa() and fb() to fight over the lock...\n", (int)pthread_self() );
 sleep(2); /* 2s should be enough for fa() to grab lock first */
 fprintf( stderr, "this is thread %d in fc(), time to send a SIGINT to my own pid and see what happens...\n", (int)pthread_self() );
 kill( getpid(), SIGINT );
 return NULL;
}

void sig( int s )
{
 fprintf( stderr, "this is thread %d in sig(): see, a blocked thread can handle an interrupt!\n", (int)pthread_self() );
 exit(0);
}

And when we run it:

$ gcc interrupt_block.c -o interrupt_block
$ ./interrupt_block
this is thread 165928960 in fa(), about to grab lock...
this is thread 167002112 in fc(), waiting a bit for fa() and fb() to fight over the lock...
this is thread 166465536 in fb(), setting up as the only SIGINT handling thread...
this is thread 165928960 in fa(), got lock, sleeping for a long time...
this is thread 166465536 in fb(), waiting a bit for fa() to grab lock first...
this is thread 166465536 in fb(), about to grab lock and hang forever waiting...
this is thread 167002112 in fc(), time to send a SIGINT to my own pid and see what happens...
this is thread 166465536 in sig(): see, a blocked thread can handle an interrupt!

I may use this as an example in class next year, but since I spent time on it, I thought others might be interested in seeing the clash of the thread titans.