c - 使函数线程安全:线程特定数据与互斥锁
问题描述
来自 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。
但我不明白:
根据定义,这样的函数是不可重入的,并且互斥体不能用于使它们成为线程安全的。
问题:
为什么说“不能使用互斥锁......而线程特定的数据可以......”?是否有任何条件可以使线程不安全的函数仅使用特定于线程的数据而不使用互斥锁成为线程安全的?
我想我可以使线程不安全成为线程安全的线程
strerror()
,只需添加一个互斥锁。与使用线程特定数据发布的相比,它有什么不同吗?(可能会失去一些并发效率?因为我要使用互斥锁来锁定访问静态变量的代码)
解决方案
我想我可以把线程不安全的 strerror() 变成一个线程安全的,只需添加一个互斥锁。
好吧,你错了,SUSv3 的作者是对的。
要了解为什么互斥锁不能使这些不可重入函数成为线程安全的,请考虑strerror
.
添加互斥锁可以使strerror
自己安全。
也就是说,我们可以避免strerror
不同线程中并发调用之间的数据竞争。
这就是我认为您的想法:在开始时锁定互斥锁,在结束时解锁,完成工作。简单的。
然而,它也是完全没有价值的——因为调用者永远不能安全地使用返回的缓冲区:它仍然与其他线程共享,并且互斥锁只在对strerror
.
使函数安全和有用(使用互斥锁)的唯一方法是让调用者保持互斥锁,直到它完成使用缓冲区,这...需要更改接口。
推荐阅读
- javascript - React Native countDown Timer 使用 Json 响应对象作为限制日期
- node.js - 在 node.js express 应用程序中,端口号由什么决定?
- c# - C# Typewriter - 如何重用模板的块
- security - 给用户一个后端看不到的秘密
- laravel - 带有哈希和 slug 参数的路由模型绑定路由
- c# - 将数据从一个列表复制到另一个列表,同时在 C# 中对其进行格式化
- node.js - 使用 Airplay 的电子镜像屏幕
- r - VBA/Excel中的自定义日期分组功能
- jmeter - JMeter - 负载测试用户场景
- django - Django 两因素身份验证 - 从向导中删除令牌生成器选项