【问题标题】:How to initialize a C++ global mutex in a thread safe way如何以线程安全的方式初始化 C++ 全局互斥锁
【发布时间】:2018-11-30 08:41:46
【问题描述】:

尝试使用函数静态互斥锁来使代码线程安全。问题不是所有编译器都以线程安全的方式初始化函数静态变量。

void Initialize()
{
  static Mutex L;   // can't be initialized at compile time because constructor calls CreateMutex()
  L.Lock()
  // call thread unsafe code
  L.Unlock()
}

以下是已经考虑过的解决方案:

  1. 在全局范围内声明 Mutex,以便在 main() 之前进行初始化。不幸的是,当有一个全局变量的构造函数调用 Initialize() 时,这不起作用,因为在 C++ 中无法保证全局变量的初始化顺序

  2. 使用原子操作

void Initialize() { static volatile uint16_t lock=0; // trivial initialization can happen @ compile time while (AtomicExchange(lock,(uint16_t)1)!=0); // swap lock with 1 and return previous value // call thread unsafe code lock=0; }

这可行,但有忙等待的缺点

  1. 使用 pthread 的编译时初始化器

    pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

不幸的是,这是针对 Windows 的,我们正在尝试避免使用 pthread

欣赏便携的解决方案。我知道在 C++ 2011 中,函数静态初始化是线程安全的,但我们避免使用 C++ 2011,因为某些嵌入式平台可能对 C++ 2011 的支持不可靠。

【问题讨论】:

  • Volatile 与线程安全无关。您可以使用en.cppreference.com/w/c/language/atomic
  • 在 C++11 之前可能没有“可移植”的方式,因为线程在 2011 年首次引入 C++。
  • 4.在函数外部创建互斥体,将其作为函数参数/上下文的一部分传递。
  • 显然Initialize 需要访问其他全局状态(否则您不需要锁定它)。为什么您认为“在全局范围内声明互斥锁”由于无序初始化而不起作用,而其他全局状态将被正确初始化?
  • @felix boost.thread 在 C++11 之前很长一段时间都可以使用

标签: c++ multithreading static initialization mutex


【解决方案1】:

IMO 解决您的问题的方法是使用漂亮的反制成语。这个成语在这里描述得很好:https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Nifty_Counter

使用此习惯用法std::coutstd::cerrstd::clog 初始化流。唯一的要求是您需要正确计算创建类型的对齐方式。使用该对齐方式,您需要创建一个全局缓冲区。使用该缓冲区,可以创建具有放置新运算符的所需类型的实例。 Boost 和 Modern C++ (>= C++11) 有 (std|boost)::aligned_storage 模板类来为你创建一个正确对齐的缓冲区。请参考:

现代版本的 C++ 具有alignof 运算符,可用于计算特定类型的对齐方式。在 boost 中,您需要使用类型特征 alignment_of: https://www.boost.org/doc/libs/1_69_0/libs/type_traits/doc/html/boost_typetraits/reference/alignment_of.htmlalignment_of 类型特征也是现代 C++ 中 STL 的一部分,但在这种特殊情况下,alignof 运算符更易于使用。

【讨论】:

  • 巧妙的技巧,但我意识到如果我使用的互斥锁几乎可以在任何地方使用(例如用于内存分配),这意味着必须将引用计数器类放入每个翻译单元中。我还在寻求编译时初始化解决方案
  • 可能我误解了一些东西,但是在每个包含标题的翻译中,引用计数器都会增加。用于放置新的缓冲区是在“编译时”或更准确地在加载时从全局内存存储而不是从堆或空闲存储分配的。
  • 我没有说解决方案不起作用。我只是说这很麻烦,因为几乎任何文件都可以有全局变量,其构造函数调用特殊的内存分配器,因此需要引用计数器来初始化互斥体
  • 您可以在某个头文件(作为全局静态变量)中创建该引用计数器,以便它在每个编译单元中。但是链接器可能会将其删除为未使用
  • IMO 我无法想象链接器会去掉引用计数。这会破坏应用程序逻辑。
猜你喜欢
  • 2021-07-24
  • 1970-01-01
  • 2012-06-05
  • 1970-01-01
  • 1970-01-01
  • 2020-04-03
  • 1970-01-01
  • 1970-01-01
  • 2016-03-04
相关资源
最近更新 更多