如何对模板化的只读单例函数进行强制求值?

How to force eager-evaluation of templated read-only singleton function?

提问人:Jeremy Friesner 提问时间:8/13/2023 最后编辑:Jeremy Friesner 更新时间:8/13/2023 访问量:37

问:

我的代码库包含这个非常简单的模板化函数,可以从很多不同的地方调用:

// Returns a read-only reference to a default-constructed
// object of the specified type
template <typename T> const T & GetDefaultObjectForType()
{
   static const T _defaultObject{};
   return _defaultObject;
}

此函数在模板化代码中很有用,因为它允许模板化代码访问其模板化的类型的默认构造对象,而不必为此目的构造一个对象。

它运行良好,但我今天遇到的问题是 helgrind 告诉我,当我有多个线程调用此函数时,存在争用条件;这是因为 G++ 的函数底层实现看起来更像是这样(伪代码):

template <typename T> const T & GetDefaultObjectForType()
{
   static char _defaultObject[sizeof(T)];
   static std::atomic<bool> _isConstructed = false;
   if (_isConstructed == false)
   {
      _isConstructed = true;
      new (_defaultObject) T();  // demand-construct the object!
   }
   return reinterpret_cast<const T &>(_defaultObject);
}

...因此,由于上述原因,告诉我线程 A 的调用 to 已写入对象(即 demand-construct 它),而线程 B 的代码在调用 to 后立即从对象中读取,因此存在争用条件。helgrindGetDefaultObjectForType()GetDefaultObjectForType()

我不确定这是否是一个真实的、有朝一日会咬我的屁股的竞争条件,或者只是由于 helgrind 过于敏感而导致的假阳性,但无论哪种情况,我的问题都是一样的:我可以使用某种技术来强制对象的构造发生在(或接近)主线的顶部,即在任何线程生成之前, 因此,就调用此函数的所有线程而言,单例对象可以真正是只读的?

显然,我可以手动调用 的顶部,但这将是一个真正的维护麻烦,因为我使用了这么多不同类型的对象,以至于我可能会忘记包含某些类型(这将迫使我必须有很多头文件,我希望它不必包含)。GetDefaultObjectForType<EveryTypeIUse>()main()main.cpp#include

多线程 C++11 模板 单例 静态初始化

评论

2赞 dewaffled 8/13/2023
这回答了你的问题吗?C++11 中的局部静态变量初始化线程安全吗?
0赞 Jeremy Friesner 8/13/2023
@dewaffled有可能,但如果是这样,它就提出了一个问题,为什么 Helgrind 会报道那里的比赛。我想知道我的 g++ 版本是否有问题,或者 helgrind 是否报告了误报,或者?
0赞 Дмитрий Воронецкий 8/13/2023
你可以看看你的函数是如何编译的: godbolt.org/z/5T8vG8feP 这里似乎有线程同步,由 __cxa_guard_acquire 完成
0赞 Jérôme Richard 8/13/2023
在这个特定的用例中,C++ 标准需要线程安全的静态初始化,这是一个非常常见的用法(例如 Meyers 的单例),所以如果它是一个 GCC 错误,那将是非常令人惊讶的。也许 Helgrind 可以报告错误阳性。对于此类工具,这种情况并不罕见(尤其是对于静态变量)。或者也许 Helgrind 中存在错误,因此它不考虑这种情况。
0赞 Yakk - Adam Nevraumont 8/15/2023
我将要求引用伪代码。你在 gcc 中断言了一个错误,除了 helgrind 抱怨之外,我看不到任何证据。更有可能的是,您的实际代码中确实存在竞争条件,并且 helgrind 没有 bug,或者 helgrind 有 bug。

答: 暂无答案