Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making a function thread safe: Thread-specific data vs mutex

From The Linux Progamming Interface, in §31.3.4 Employing the Thread-Specific Data API, it gives a good example of using thread-specific data to make a thread-unsafe function thead-safe:

Thead-unsafe version:

/* Listing 31-1 */

/* strerror.c

   An implementation of strerror() that is not thread-safe.
*/
#define _GNU_SOURCE                 /* Get '_sys_nerr' and '_sys_errlist'
                                       declarations from <stdio.h> */
#include <stdio.h>
#include <string.h>                 /* Get declaration of strerror() */

#define MAX_ERROR_LEN 256           /* Maximum length of string
                                       returned by strerror() */

static char buf[MAX_ERROR_LEN];     /* Statically allocated return buffer */

char *
strerror(int err)
{
    if (err < 0 || err >= _sys_nerr || _sys_errlist[err] == NULL) {
        snprintf(buf, MAX_ERROR_LEN, "Unknown error %d", err);
    } else {
        strncpy(buf, _sys_errlist[err], MAX_ERROR_LEN - 1);
        buf[MAX_ERROR_LEN - 1] = '\0';          /* Ensure null termination */
    }

    return buf;
}

Thread-safe version with thread-specific data:

/* Listing 31-3 */

/* strerror_tsd.c

   An implementation of strerror() that is made thread-safe through
   the use of thread-specific data.

   See also strerror_tls.c.
*/
#define _GNU_SOURCE                 /* Get '_sys_nerr' and '_sys_errlist'
                                       declarations from <stdio.h> */
#include <stdio.h>
#include <string.h>                 /* Get declaration of strerror() */
#include <pthread.h>
#include "tlpi_hdr.h"

static pthread_once_t once = PTHREAD_ONCE_INIT;
static pthread_key_t strerrorKey;

#define MAX_ERROR_LEN 256           /* Maximum length of string in per-thread
                                       buffer returned by strerror() */

static void                         /* Free thread-specific data buffer */
destructor(void *buf)
{
    free(buf);
}

static void                         /* One-time key creation function */
createKey(void)
{
    int s;

    /* Allocate a unique thread-specific data key and save the address
       of the destructor for thread-specific data buffers */

    s = pthread_key_create(&strerrorKey, destructor);
    if (s != 0)
        errExitEN(s, "pthread_key_create");
}

char *
strerror(int err)
{
    int s;
    char *buf;

    /* Make first caller allocate key for thread-specific data */

    s = pthread_once(&once, createKey);
    if (s != 0)
        errExitEN(s, "pthread_once");

    buf = pthread_getspecific(strerrorKey);
    if (buf == NULL) {          /* If first call from this thread, allocate
                                   buffer for thread, and save its location */
        buf = malloc(MAX_ERROR_LEN);
        if (buf == NULL)
            errExit("malloc");

        s = pthread_setspecific(strerrorKey, buf);
        if (s != 0)
            errExitEN(s, "pthread_setspecific");
    }

    if (err < 0 || err >= _sys_nerr || _sys_errlist[err] == NULL) {
        snprintf(buf, MAX_ERROR_LEN, "Unknown error %d", err);
    } else {
        strncpy(buf, _sys_errlist[err], MAX_ERROR_LEN - 1);
        buf[MAX_ERROR_LEN - 1] = '\0';          /* Ensure null termination */
    }

    return buf;
}

And in the Summary section of this chapter it says:

...
Most of the functions specified in SUSv3 are required to be thread-safe. SUSv3 also lists a small set of functions that are not required to be thread-safe. Typically, these are functions that employ static storage to return information to the caller or to maintain information between successive calls. By definition, such functions are not reentrant, and mutexes can’t be used to make them thread-safe. We considered two roughly equivalent coding techniques—thread-specific data and thread-local storage—that can be used to render an unsafe function thread-safe without needing to change its interface.
...

I understand that using thread-specific data aims to make the thread-unsafe function to a thread-safe one, without changing the function's interface/signature.

But I don't understand:

By definition, such functions are not reentrant, and mutexes can’t be used to make them thread-safe.

Question:

  1. Why is it saying that "mutexes can't be use... while thread-specific data can...."? Is there any conditions that I can make a thread-unsafe function thread-safe only with thread-specific data but not with mutex?

  2. I think I can make the thread-unsafe strerror() to a thead-safe one, simply adding a mutex. Does it make any difference compared to the posted one using thread-specifia data? (Maybe lost some concurrent efficiency? Since I am gonna use a mutex to lock the code accessing the static variable)

like image 654
Rick Avatar asked Jun 18 '26 02:06

Rick


1 Answers

I think I can make the thread-unsafe strerror() to a thead-safe one, simply adding a mutex.

Well, you're wrong, and the authors of SUSv3 are right.

To see why a mutex can't make these non-reentrant functions threadsafe, consider the original (unsafe) code for strerror.

Adding a mutex can make strerror itself safe.

That is, we can avoid data races between concurrent calls to strerror in different threads.

This is what I think you had in mind: lock a mutex at the start, unlock at the end, job done. Easy.

It is also, however, entirely worthless - because the caller can never safely use the returned buffer: that's still shared with other threads and the mutex only protects it inside the call to strerror.

The only ways to make the function safe and useful (using mutexes) is for the caller to hold the mutex until it has finished using the buffer, which ... requires an interface change.

like image 90
Useless Avatar answered Jun 21 '26 00:06

Useless



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!