我有一堆线程。他们应该访问一个包含配置数据的单例,该配置数据在创建单例时初始化一次。因此在第一次访问时。所以对单例的进一步操作只是只读的。在这种情况下我需要关键部分吗?
5 回答
似乎因为数据是在第一次访问时延迟创建的,所以指针或对单例的引用是读写的。这意味着您确实需要一个关键部分。
事实上,在这种情况下,在保持惰性初始化的同时避免临界区的愿望是如此普遍,以至于它导致了双重检查锁定反模式的创建。
另一方面,如果您在读取之前急切地初始化您的单例,您将能够避免通过常量指针/引用访问不可变对象的关键部分。
我理解您的问题,因为您的单例中有延迟初始化。它仅在第一次读取时初始化。
接下来的连续读取是线程安全的。但是初始化期间的并发读取呢?
如果你有这样的情况:
SomeConfig& SomeConfig::getInstance()
{
static SomeConfig instance;
return instance;
}
然后它取决于你的编译器。根据 C++03 中的这篇文章,如果此静态初始化是线程安全的,则取决于实现。
对于 C++11,它是线程安全的 - 请参阅这篇文章的答案,引用:
这样的变量在控制第一次通过其声明时被初始化;这样的变量在其初始化完成时被认为已初始化。[...]如果在初始化变量时控制同时进入声明,则并发执行应等待初始化完成。
值得注意的是,对全局变量的只读访问是线程安全的。
不会。如果您只是在完全初始化后读取此数据并且数据从未更改,那么就不可能发生竞争条件。
但是,如果数据以任何方式被写入/修改,那么您将需要同步对它的访问,即在写入之前锁定数据。
如果您只读取一些共享数据,而从不写入,则不需要同步访问。
您只需要在可能同时读取和写入共享数据时进行同步。
规范中的官方规则是数据竞争是指一个线程可以同时写入一个变量,而另一个线程读取或写入同一个变量。
如果您可以证明必须在任何读者可以阅读之前进行初始化,那么您就不需要同步。这通常通过在创建(或同步)线程之前进行初始化,或者通过使用静态存储变量来完成,C++11 保证了一些同步