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

Thread-safely initializing a pointer just once

我正在编写一个库函数,比如说,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);
}

并依靠 static 的 C++ 语义来保证在没有任何竞争条件的情况下只调用一次。但是在纯 C 中执行此操作的最佳方法是什么?

这是我想出的:

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

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

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

那么,最好的选择是什么?我还漏掉了什么吗?

如果您可以使用 C11,这将有效(假设您的实现支持线程 - it's an optional feature):

#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 );
}

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