【发布时间】:2020-03-16 09:56:12
【问题描述】:
此代码是否使用 C++11 atomic 安全地实现了双重检查习惯用法?
我在“C++ 编程语言第 4 版”中看到。一个使用atomic<bool> 的示例,我尽力保持相同,但我不自信。另外,这个可以改进吗?
由于once_flag 的存储开销,我想避免使用call_once。
这个“LazyArray”是为了减少内存而编写的,目的是用对客户端代码的最小更改来替换数组(预计只有一小部分元素会被访问)。从多个线程访问数组是不争的事实,并且由于性能的原因,广泛的锁定会出现问题。
/**
* Lazy creation of array elements.
* They will only be created when they are referenced.
*
* This "array" does not support iteration because it can have holes
*
* Array bounds isn't checked to keep it fast.
* Destruction of the array destroys all the T objects (via the unique_ptr d'tor)
*/
template<class T, size_t size>
class LazyArray
{
typedef LazyArray<T, size> mytype;
public:
// copying is not allowed (unlike regular arrays)
LazyArray(const LazyArray&) = delete;
LazyArray& operator=(const LazyArray&) = delete;
LazyArray(){}
T& operator[](size_t i)
{
return at(i);
}
const T& operator[](size_t i) const
{
return const_cast<mytype *>(this)->at(i);
}
private:
using guard = std::lock_guard<std::mutex>;
// get T object at index i by reference
T& at(size_t i) // only non-const variant is implemented, const version will use const_cast
{
auto &p = m_array[i];
std::atomic<T*> ap(p.get());
if(!ap) // object not created yet
{
guard g(mtx);
ap = p.get();
if(!ap)
p.reset(new T);
}
return *p;
}
std::unique_ptr<T> m_array[size];
std::mutex mtx;
};
【问题讨论】:
-
请记住,只有当
sizeof(T)远大于sizeof(unique_ptr<T>)(通常是指针的大小,例如 8 字节)时,这才有意义。您已经有一个sizeunique_ptr 对象数组使用了空间。引入另一个级别的间接也不利于性能,并且如果您遍历数组的顺序元素,可能会导致更糟糕的局部性。 -
理想情况下,您应该为
T的数组直接分配连续内存,但不要触摸它,因此操作系统的惰性分配机制可以为您发挥作用(保持新鲜来自操作系统的虚拟内存页面延迟为零/COW 映射到操作系统的零页面),直到第一次读取或写入。但是你需要知道何时使用placement-new通过单独的簿记或T中的哨兵字段来构造新元素。即知道T的某些部分在已经构造的对象中不能是0。 -
你应该使用 const 和
const_cast来实现你的非常量函数,而不是相反。非 const 函数可以抛弃 const。由于通过 const 函数中的const_cast进行成员修改,当前代码中的问题是未定义的行为。 -
@Darhuuk,也许在这种情况下应该删除 const 变体,因为如果 LazyArray 是 const,这意味着每个
unique_ptr也是 const 并且不能安全地重置对吧?没有 const 调用非 const 变体,两者都调用at() -
@CplusPuzzle 我的观点是你的 const 函数修改了类的成员(在这种情况下是互斥锁,在非常量函数
at中)。通常不会编译。因为const_cast确实如此,但现在它是未定义的行为。
标签: c++ multithreading locking stdatomic double-checked-locking