首页 > 解决方案 > 使函数线程安全:线程特定数据与互斥锁

问题描述

来自 The Linux Progamming Interface,在§31.3.4 Employing the Thread-Specific Data API中,它提供了一个使用线程特定数据来使线程不安全函数 thead-safe 的一个很好的例子:

不安全的版本:

/* 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;
}

具有线程特定数据的线程安全版本:

/* 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;
}

在本章的总结部分,它说:

...
SUSv3 中指定的大多数函数都需要是线程安全的。SUSv3 还列出了一小组不需要线程安全的函数。通常,这些函数使用静态存储向调用者返回信息或在连续调用之间维护信息。根据定义,这样的函数是不可重入的,并且互斥体不能用于使它们成为线程安全的。我们考虑了两种大致等效的编码技术——特定于线程的数据和线程本地存储——它们可用于将不安全的函数呈现为线程安全的,而无需更改其接口。
...

我了解使用特定于线程的数据旨在使线程不安全的函数成为线程安全的函数,而无需更改函数的 interface/signature

但我不明白:

根据定义,这样的函数是不可重入的,并且互斥体不能用于使它们成为线程安全的。

问题:

  1. 为什么说“不能使用互斥锁......而线程特定的数据可以......”?是否有任何条件可以使线程不安全的函数仅使用特定于线程的数据而不使用互斥锁成为线程安全的?

  2. 我想我可以使线程不安全成为线程安全的线程strerror(),只需添加一个互斥锁。与使用线程特定数据发布的相比,它有什么不同吗?(可能会失去一些并发效率?因为我要使用互斥锁来锁定访问静态变量的代码)

标签: cmultithreadingunixthread-safetymutex

解决方案


我想我可以把线程不安全的 strerror() 变成一个线程安全的,只需添加一个互斥锁。

好吧,你错了,SUSv3 的作者是对的。

要了解为什么互斥锁不能使这些不可重入函数成为线程安全的,请考虑strerror.

添加互斥锁可以使strerror自己安全。

也就是说,我们可以避免strerror不同线程中并发调用之间的数据竞争。

这就是我认为您的想法:在开始时锁定互斥锁,在结束时解锁,完成工作。简单的。

然而,它也是完全没有价值的——因为调用者永远不能安全地使用返回的缓冲区:它仍然与其他线程共享,并且互斥锁只strerror.

使函数安全和有用(使用互斥锁)的唯一方法是让调用者保持互斥锁,直到它完成使用缓冲区,这...需要更改接口。


推荐阅读