我有一个我用互斥锁保护的集合。初始化之后它只能被读取,所以我不需要那里的互斥量。
初始化集合并在全局静态初始化程序中填充。我知道在单个翻译单元中保证全局静态初始化。是否可以保证全局静态初始化将是单线程的?
我有一个受Schwarz计数器保护的静态集合,并由其他静态对象的构造函数填充。容器与互斥锁相关联。鉴于在main
启动后集合是只读的,我想摆脱互斥锁,如果我可以保证在单个线程中调用静态构造函数。
我的理解是静态初始化顺序通常在单个翻译单元中很好地定义,但在翻译单元之间未指定。标准是否允许由不同的运行时提供的线程初始化/构造静态对象?
施瓦茨专柜:
标题文件:
struct Init
{
Init();
~Init();
};
namespace
{
Init _init;
}
extern std::map<int, std::unique_ptr<...>> &protected;
源文件:
namespace
{
int init_count;
std::aligned_storage<sizeof(std::map<int, std::unique_ptr<...>>), alignof(std::map<int, std::unique_ptr<...>>>)> protected_storage;
}
std::map<int, std::uniqe_ptr<...>> &protected = *reinterpret_cast<std::map<int, std::unique_ptr<...>> *>(&protected_storage);
Init::Init()
{
if (!init_counter++)
{
new(&protected_storage) std::map<int, std::unique_ptr<...>>();
}
}
Init::~Init()
{
if (!--init_counter)
{
protected.~std::map<int, std::unique_ptr<...>>();
}
}
收集人口:
struct helper
{
helper(...)
{
protected.insert(std::make_pair(...));
}
};
扩展了一个宏,用于创建帮助程序的静态实例。
答案 0 :(得分:4)
是否可以保证全局静态初始化将是单线程的?
你的意思是动态初始化。不,显然无法保证单线程初始化。
从3.6.2:
如果程序启动一个线程(30.3),则进行后续初始化 对于在不同转换中定义的变量的初始化,变量的变量是未排序的 单元。否则,变量的初始化相对于初始化是不确定地排序的 在不同的翻译单元中定义的变量。如果程序启动一个线程,则后续无序 对于每个其他动态初始化,变量的初始化是未排序的。除此以外, 对于每个其他动态,变量的无序初始化是不确定地排序的 初始化
因此,如果你在程序中启动一个线程,那么两个不同的TU中的两个不同的全局变量理论上可以让它们的构造函数同时从两个不同的线程运行。
处理这些问题的最佳方法是将静态存储持续时间变量包装为以下“单例模式”中的本地静态变量:
const T& f()
{
static T t(a,b,c);
return t;
}
最新标准保证t
的构造是线程安全的,因此根本不需要互斥锁(至少没有明确指定,编译器会为你生成防护)。
作为一个额外的好处,该对象在第一次调用f
时“懒洋洋地”构建,因此您无需担心初始化顺序。如果多个这样的单例在它们的构造函数中相互调用(假设依赖关系当然是非循环的),它们将在工作顺序中初始化。对于非局部变量,情况并非如此。