setlocale函数是线程安全的吗?

9
我需要在线程中更改语言环境以正确解析双精度数,我正在使用setlocale()进行此操作(C++)。这是线程安全的吗?
更新:另一个问题。当我在main()函数中调用setlocale()时,它不会影响更深层次的其他例程。为什么?由于代码很多,所以编写代码块有问题。
6个回答

8

在 C++11 标准中,线程是语言的一部分。标准明确指出,setlocale() 调用会与其他调用 setlocale() 或调用受当前 C 语言环境影响的函数引入数据竞争,包括 strtod()。locale::global() 函数被认为与 setlocale() 齐名,因此也可能导致数据竞争(如下所示)。

在 Linux 上,使用 glibc 来说,当线程并发调用 setlocale() 并带有非空参数,并调用可能使用全局语言环境的任何其他函数时,这种情况是 MT-unsafe (const:locale env) 的,这会导致数据竞争并引起未定义行为。建议改用只更改调用线程语言环境且是 MT-safe 的 uselocale()。在 C++ 代码中,如果在 Linux 上使用 libstdc++,应避免 locale::global (进程范围更改),而应为线程创建语言环境(由于与 C 运行时相同的原因,locale::global 是 MT-unsafe)。鉴于您的目标是使用 strtod(C API),应使用 uselocale()。

在Linux上,使用glibc时,setlocale()函数本身是MT-unsafe的,除非你满足两个严格的条件,并且根据POSIX的要求,会改变整个进程的语言环境。新的Linux man页面(Red Hat和Fujitsu致力于为所有API指定MT-safety注释的一部分)将setlocale()标记为“MT-Unsafe const:locale env”,这意味着只有在保持语言环境恒定(通过不修改它,仅通过传递NULL查询它),并且保持语言环境和环境恒定(以避免参数为""时更改语言环境)的情况下,setlocale才是MT-safe的。在Linux上,如果您想仅更改调用线程的语言环境,则应使用uselocale(),因为这是MT-safe的,而且不以任何方式依赖于您的环境,strtod将使用线程的语言环境。同样,所有实现POSIX的系统都应该提供uselocale()供在线程上下文中使用(MT-safe)。
OS X实现了uselocale(),所以你可以使用它。
在Windows上,使用_configthreadlocale来更改setlocale()操作整个进程或线程(将其转换为所需的uselocale),但对于C++代码,您应该再次使用locale类的实例并避免使用locale::global。

在Windows上,您应该使用特殊的I18N格式函数,如GetDateFormatEx,而不是strftime等。在macOS上,即使不进行cocoa GUI,也应使用NSFormater。 Unix提供的1980年代解决方案真的很糟糕。 - Lothar
好的写作。你有任何参考资料吗? - Martin Ba
添加了对上游手册工作和glibc手册页面的引用,以及MT安全性说明。 - Carlos O'Donell
新增对较新的C++11特性的引用,其中包括线程和有关setlocale()的说明。 - Carlos O'Donell
有趣的一点是:我找不到任何文档证明 _configthreadlocale 是否实际上是线程安全的。从文档中可以看出:“如果您使用 _configurethreadlocale 启用每个线程的区域设置,则建议您立即在该线程中调用 setlocale 或 _wsetlocale 来设置首选区域设置。”由于我会在线程中调用 setlocale,因此我还需要在那里调用 _configurethreadlocale。 - Samuel

7

调用setlocale()函数可能是线程安全的,也可能不是,但是区域设置本身是每个进程而非每个线程的。这意味着即使你的setlocale()是线程安全的或者你使用互斥锁来保护自己,改变仍将更新所有线程的当前区域设置。

不过,有一种针对每个线程的替代方法:uselocale()。

#include <xlocale.h>

locale_t loc = newlocale(LC_ALL_MASK, "nl_NL", NULL);
uselocale(loc);
freelocale(loc)
// Do your thing

locale在内部使用引用计数,这就是为什么在您使用newlocale()激活它后可以安全释放它的原因。


1
更准确地说,区域设置可以是每个进程的,参见https://msdn.microsoft.com/en-us/library/26c0tb7x.aspx。 - Lev

3
您需要查阅您所使用的实现的文档。C++当前没有关于线程的任何规定,因此这取决于实现(您尚未告诉我们哪个实现)。
例如,我的Linux手册中有setlocale的片段:
“此字符串可以在静态存储器中分配。”
这并不绝对表明它是不安全的线程,但我会非常谨慎。调用它时,如果使用NULL(即查询),可能是线程安全的,但一旦有一个线程修改它,所有的赌注都将失效。
setlocale调用的最安全的事情(假设它不是线程安全的)可能是使用互斥锁保护所有调用,并具有类似以下内容的特殊函数来格式化您的数字:
claim mutex
curr = setlocale to specific value
format number to string
setlocale to curr
release mutex
return string

较新的Linux man-page项目更新绝对包含MT-safety信息。向下滚动页面到“ATTRIBUTES”以查看定义函数安全性的“MT-unsafe const:locale env”。这种标记是Linux man-pages项目和glibc之间为开发人员使用而进行了一年的合作的一部分。然后,查看'man 7 attributes',以了解如何绕过const:locale和env标记来尝试使其安全。 - Carlos O'Donell

2
对于 C++98,这取决于编译器和您选择的运行时库以及您所指的“线程安全”的确切含义。例如,在使用 MSVC 和多线程运行时的情况下,您应该是安全的,就像 setlocale 本身一样。但我认为您不会得到每个线程的区域设置。请使用 setlocale 设置全局区域设置,而不是每个线程的区域设置。C++98 不涉及线程(或动态库)问题。

1

这是一个微软特定的扩展,不是C语言的一部分。 - IInspectable

0

回答原问题的第二个部分:

setlocale函数在C库中(在标准头文件<clocale>中定义在C++环境中),使用它只会影响C库例程。你在问题的第一部分提到了C ++,我想知道您是否希望C ++例程注意使用setlocale进行区域设置更改。我的经验表明他们不会。

C++处理区域信息的正确方法是由标准C++头文件<locale>中指定的库定义的。该库以与C++ I/O操作兼容的方式控制区域信息。例如,您可以创建具有某些特征的std::locale对象,然后用该对象灌输一个std::filebuf,以便I/O操作遵循这些特征。

如果您正在运行混合的C / C ++环境,请使用std::locale :: global() - 使用正确类型的参数,它还将设置C全局区域设置,就像调用LC_ALL的C库函数setlocale一样。这将使C和C ++库功能保持同步。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接