线程安全地初始化一个指针一次

0xd*_*00d 3 c multithreading initialization

例如,我正在编写一个库函数,count_char(const char *str, int len, char ch)它检测正在运行的 CPU 支持的 SIMD 扩展并将调用分派到 AVX2 或 SSE4.2 优化版本。因为我想避免cpuid每次调用执行几个指令的惩罚,所以我试图在第一次调用函数时只执行一次(可能由不同的线程同时调用)。

在 C++ 领域,我只会做类似的事情

int count_char(const char *str, int len, char ch) {
    static const auto fun_ptr = select_simd_function();
    return (*fun_ptr)(str, len, ch);
}
Run Code Online (Sandbox Code Playgroud)

并依赖于 C++ 语义static来保证它只被调用一次而没有任何竞争条件。但是在纯 C 中做到这一点的最佳方法是什么?

这是我想出的:

  1. 使用原子变量(也存在于 C 中)——相当容易出错并且更难维护。
  2. 使用pthread_once- 不确定它有什么开销,而且它可能会给 Windows 带来麻烦。
  3. 强制库用户调用另一个库函数来初始化指针——简而言之,它在我的情况下不起作用,因为这实际上是另一种语言的库的 C 位。
  4. 将指针对齐 8 个字节并依赖 x86 字大小的访问是原子的——不可移植到其他架构(我以后是否应该实现一些 PowerPC 或特定于 ARM 的 SIMD 版本,说),技术上 UB(至少在 C++ 中)。
  5. 使用线程本地存储并标记fun_ptrthread_local然后执行类似的操作
    static thread_local fun_ptr_t fun_ptr = NULL;
    if (!fun_ptr) {
        fun_ptr = select_simd_function();
    }
    return (*fun_ptr)(str, len, ch);
Run Code Online (Sandbox Code Playgroud)

好处是代码非常清晰并且显然是正确的,但我不确定 TLS 的性能影响,而且每个线程都必须调用select_simd_function()一次(但这可能没什么大不了的)。

就我个人而言,到目前为止,(5) 是赢家,紧随其后的是 (1)(如果不是其他人非常基础的图书馆,我什至可能会选择 (1),而且我不想让自己难堪一个可能的错误实现)。

那么,最好的选择是什么?我错过了什么吗?

And*_*nle 5

如果您可以使用 C11,这将起作用(假设您的实现支持线程 -这是一个可选功能):

#include <threads.h>

static fun_ptr_t fun_ptr = NULL;

static void init_fun_ptr( void )
{
    fun_ptr = select_simd_function();
}

fun_ptr_t get_simd_function( void )
{
    static once_flag flag = ONCE_FLAG_INIT;

    call_once( &flag, init_fun_ptr);

    return ( fun_ptr );
}
Run Code Online (Sandbox Code Playgroud)

当然,你提到了Windows。我怀疑 MSVC 是否支持这一点。