Thread-Specific Data and Signal Handling in Multi-Threaded Applications

Here are the answers to questions about signal handling and taking care of global data when writing multi-threaded programs.
Signal Handling

Many people find signal handling in C to be a bit tricky at the best of times. Multi-threaded applications need a little extra care when it comes to signal handling, but once you've written two programs, you'll wonder what all the fuss was about—trust me. And if you start to panic, remember—deep, slow breaths.

A quick re-cap of what signals are. Signals are the system's way of informing a process about various events. There are two types of signals, synchronous and asynchronous.

Synchronous signals are a result of a program action. Two examples are:

  1. SIGFPE, floating-point exception, is returned when the program tries to do some illegal mathematical operation such as dividing by zero.

  2. SIGSEGV, segmentation violation, is returned when the program tries to access an area of memory outside the area it can legally access.

Asynchronous signals are independent of the program. For example, the signal sent when the user gives the kill command.

In non-threaded applications there are three usual ways of handling signals:

  1. Pretend they don't exist, perhaps the most common policy, and quite adequate for lots of simple programs—at least until you want your program to be reliable and useful.

  2. Use signal() to set up a signal handler—nice and simple, but not very robust.

  3. Use the POSIX signal handling functions such as sigaction() and sigprocmask() to set up a signal handler or to ignore certain signals—the “proper” method.

If you choose the first option, then signals will have some default behavior. Typically, this default behavior will cause the program to exit or cause the program to ignore the signal, depending on what the signal is. The latter two options allow you to change the default behavior for each signal type—ignore the signal, cause the program to exit or invoke a signal-handling function to allow your program to perform some special processing. Avoid the use of the old-style signal() function. Whether you're writing threaded or non-threaded applications, the extra complications of the POSIX-style functions are worth the effort. Note that the behavior of sigprocmask(), which sets a signal mask for a process, is undefined in a multi-threaded program. There is a new function, pthread_sigmask(), that is used in much the same way as sigprocmask(), but it sets the signal mask only for the current thread. Also, a new thread inherits the signal mask of the thread that created it; so a signal mask can effectively be set for an entire process by calling pthread_sigmask() before any threads are created.

In a multi-threaded application, there is always the question of which thread the signal will actually be delivered to. Or does it get delivered to all the threads?

To answer the last question first, no. If one signal is generated, one signal is delivered, so any single signal will only be delivered to a single thread.

So which thread will get the signal? If it is a synchronous signal, the signal is delivered to the thread that generated it. Synchronous signals are commonly managed by having an appropriate signal handler set up in each thread to handle any that aren't masked. If it is an asynchronous signal, it could go to any of the threads that haven't masked out that signal using sigprocmask(). This makes life even more complicated. For instance, suppose your signal handler must access a global variable. This is normally handled quite happily by using mutex, as follows:

void signal_handler( int sig )
{
        ...
        pthread_mutex_lock( &mutex1 );
        ...
        pthread_mutex_unlock( &mutex1 );
        ...
}

Looks fine at first sight. However, what if the thread that was interrupted by the signal had just itself locked mutex1? The signal_handler() function will block, and will wait for the mutex to be unlocked. And the thread that is currently holding the mutex will not restart, and so will not be able to release the mutex until the signal handler exits. A nice deadly embrace.

So a common way of handling asynchronous signals in a multi-threaded program is to mask signals in all the threads, and then create a separate thread (or threads) whose sole purpose is to catch signals and handle them. The signal-handler thread catches signals by calling the function sigwait() with details of the signals it wishes to wait for. To give a simple example of how this might be done, take a look at Listing 4.

As mentioned earlier, a thread inherits its signal mask from the thread which creates it. The main() function sets the signal mask to block all signals, so all threads created after this point will have all signals blocked, including the signal-handling thread. Strange as it may seem at first sight, this is exactly what we want. The signal-handling thread expects signal information to be provided by the sigwait() function, not directly by the operating system. sigwait() will unmask the set of signals that are given to it, and then will block until one of those signals occurs.

Also, you might think that this program will deadlock, if a signal is raised while the main thread holds the mutex sig_mutex. After all, the signal handler tries to grab that same mutex, and it will block until that mutex comes free. However, the main thread is ignoring signals, so there is nothing to prevent another thread from gaining control while the signal handling thread is blocked. In this case, sig_handler() hasn't caught a signal in the usual, non-threaded sense. Instead it has asked the operating system to tell it when a signal has been raised. The operating system has performed this function, and so the signal handling thread becomes just another running thread.

______________________

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

SIGALRM - sync or async?

MarkBerg's picture

Is SIGALRM synchronous or async?
i.e. if I use alarm() within a thread, is this thread guaranteed to get the signal, or might some other thread get it?

Listing 4 Contains A Race Condition

Oldfield's picture

Do not rely on this method of deliving signal information to threads.

A race occurs if two or more signals arrive in similar timeframes - there is nothing to prevent "handled_signal" from being overwritten prior to another thread being scheduled and told about the first delivered signal.

Bad advice from the journal!

regarding synchronous signals

sravan's picture

hi,

In the Listing 4 code, the signal handler captures only the asynchronous signals(like SIGINT) but not synchronous signals like (SIGFPE,SIGSEGV etc.).

How can i make sure that the dedicated thread does the job of capturing both types of signals?

Thank you

catching SIGFPE and SIGSEGV

Anonymous's picture

signals SIGSTOP and SIGSEGV cannot be caught or ignored. I am not sure about SIGFPE....according to POSIX ignoring SIGFPE results in undefined behavior....I have tried to catch SIGFPE on my Linux system...but programme terminated with "Floating point exception"

SIGSEGV and SIGFPE can certainly be caught

Darren Kulp's picture

SIGSEGV and SIGFPE can certainly be caught; in fact, I have intentionally generated and caught both in the same program, to sandbox pseudo-simulated code. The SIGSEGV signal is thrown not only when a "truly invalid" memory access is made, but when permissions are incorrect on the desired page; it is possible to change the permissions on the page from within the handler, and return to the original code (this is what my handler did).

From the sigaction man page:

signum specifies the signal and can be any valid signal except SIGKILL and SIGSTOP.

Thread safety

C2H5OH's picture

Regarding Listing 3. I believe not all malloc() implementations are thread-safe (in fact, very few of them actually are); so I'll assume that calling malloc inside a thread without a mutex is a typo.

My real question is. The key destructor in pthread_key_create(), from where is it called? The main thread or the same thread that is terminating? Is not clear anywhere. Perhaps I should check the POSIX specification but it is also difficult to find.

Cheers

Re: Thread safety

Anonymous's picture

That is totally bogus. Any _sane_ environment that supports threads also has thread-safe malloc() and other primitives.

Thanks a million ...

Anonymous's picture

... for posting this article

Cheers !

Re: Thread-Specific Data and Signal Handling in Multi-Threaded A

Anonymous's picture

can anybody give an example code on how to make "errno" as thread specific data.

thanks in advance

rksoni@indts.com

errno should be automatically

Anonymous's picture

errno should be automatically thread specific, if you compile and link with the right flags (if required by your compiler). You shouldn't have to do anything special to make it thread specific.

Getting good info on pthreads, sockets, signals

Anonymous's picture

Unix Systems Programming (Communication, Concurrency and Threads)
by Robbins & Robbins [Pearson Education]

It has got a good coverage of all the related areas on sockets, threads and signals all in one place. Very good book.

Webinar
One Click, Universal Protection: Implementing Centralized Security Policies on Linux Systems

As Linux continues to play an ever increasing role in corporate data centers and institutions, ensuring the integrity and protection of these systems must be a priority. With 60% of the world's websites and an increasing share of organization's mission-critical workloads running on Linux, failing to stop malware and other advanced threats on Linux can increasingly impact an organization's reputation and bottom line.

Learn More

Sponsored by Bit9

Webinar
Linux Backup and Recovery Webinar

Most companies incorporate backup procedures for critical data, which can be restored quickly if a loss occurs. However, fewer companies are prepared for catastrophic system failures, in which they lose all data, the entire operating system, applications, settings, patches and more, reducing their system(s) to “bare metal.” After all, before data can be restored to a system, there must be a system to restore it to.

In this one hour webinar, learn how to enhance your existing backup strategies for better disaster recovery preparedness using Storix System Backup Administrator (SBAdmin), a highly flexible bare-metal recovery solution for UNIX and Linux systems.

Learn More

Sponsored by Storix