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

标签 c multithreading initialization

我正在编写一个库函数,比如说,count_char(const char *str, int len, char ch) 检测它正在运行的 CPU 支持的 SIMD 扩展并将调用分派(dispatch)给,比如说,一个 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 然后执行类似的操作
    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 是否支持这一点。

关于c - 线程安全地初始化一个指针一次,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60215029/

相关文章:

c - 如何检查指针是否在分配的内存中写入?

不能使用 const 全局变量作为数组号。结构体中的元素数量

java - 减少有许多 hibernate 线程时使用的操作系统线程数

multithreading - 如何在执行后台任务时呈现 View Controller ?

c++ - 用变量初始化 vector ,C++

c - murmurhash3 中的参数是什么意思?

c - 如何包含不同文件中的函数? C

java - 当- A. 从 Runnable 调用 Activity 方法时会发生什么 B. 多个线程同时调用 Activity 方法

c++ - 使用构造函数进行引用初始化

c++ - 为循环优雅地并行初始化 openmp 线程