无锁重载和共享 const 对象
Posted
技术标签:
【中文标题】无锁重载和共享 const 对象【英文标题】:Lockfree Reloading and Sharing of const objects 【发布时间】:2016-10-25 11:36:42 【问题描述】:我的应用程序的配置是一个与多个线程共享的 const 对象。配置存储在一个集中位置,任何线程都可以访问它。我已经尝试构建一个无锁实现,它允许我加载新配置,同时仍然允许其他线程读取最后一个已知配置。
我当前的实现在更新 shared_ptr
和读取它之间存在竞争。
template<typename T>
class ConfigurationHolder
public:
typedef std::shared_ptr<T> SPtr;
typedef std::shared_ptr<const T> CSPtr;
ConfigurationHolder() : m_active(new T())
CSPtr get() const return m_active; // RACE - read
template<typename Reloader>
bool reload(Reloader reloader)
SPtr tmp(new T());
if (!tmp)
return false;
if (!reloader(tmp))
return false;
m_active=tmp; // RACE - write
return true;
private:
CSPtr m_active;
;
我可以添加一个shared_mutex
来解决对shared_ptr
的有问题的读/写访问,但我正在寻找一种能够保持实现无锁的解决方案。
编辑:我的 GCC 版本不支持 atomic_exchange
shared_ptr
EDIT2:需求说明:我有多个阅读器,并且可能有多个重新加载器(尽管这种情况不太常见)。读者需要持有一个配置对象,并且在阅读时它不会改变。旧的配置对象必须在最后一个读取器完成后释放。
【问题讨论】:
你能详细说明用例吗?如果您有很多读者,并且一个线程决定重新加载,那么 1)您需要一个信号量,而不是互斥量 2)其他线程应该何时开始读取新值?直到他们应该阅读旧版本时才明确定义。 @kabanus 编辑澄清。另外,如果可能的话,我想避免添加互斥体/信号量。 您正在准确地描述读写信号量。读取操作是免费的,只有在信号量明确的情况下才能进行写入。你用计数器实现的任何东西都是这样的。假设指针分配不是原子的(它可能与 std::atomicshared_mutex
,但读取权限不是免费的。总是有一个锁来测试作家的状态。但无论如何,如果可能的话,我正在寻找无锁解决方案。
最后一条评论,为了避免讨论——可以编写一个信号量类/共享互斥类,它不会阻塞读取,但会阻塞写入。编辑: boost shared_mutex 就是这样:boost.org/doc/libs/1_39_0/doc/html/thread/…
【参考方案1】:
你应该更新你的编译器来获得原子共享指针操作。
如果失败,请将其包装在 shared_timed_mutex
中。然后测试一下你花了多少钱。
与正确编写自己的无锁共享指针系统相比,这两者的工作量要少。
如果你必须:
这是一个 hack,但它可能会起作用。它是指针本身的读取-复制-更新样式。
有一个std::vector<std::unique_ptr<std::shared_ptr<T>>>
。有一个std::atomic<std::shared_ptr<T> const*>
“当前”指针和一个std::atomic<std::size_t> active_readers
。
vector
存储您还活着的shared_ptr
s。当你想改变时,在后面推一个新的。保留此shared_ptr
的副本。
现在将“当前”指针换成新的。忙——等到active_readers
达到零,或者直到你感到无聊。
如果active_readers
命中零,则将vector
过滤为使用计数为1 的shared_ptr
s。将它们从vector
中删除。
无论如何,现在将额外的shared_ptr
删除到您创建的状态。写完了。
如果您需要多个写入器,请使用单独的互斥锁锁定此进程。
在阅读器端,增加active_readers
。现在以原子方式加载“当前”指针,制作指向shared_ptr
的本地副本,然后递减active_readers
。
但是,我只是写了这个。所以它可能包含错误。证明并发设计正确是困难。
到目前为止,最简单的方法是升级编译器并在 shared_ptr 上进行原子操作。
这可能过于复杂,我认为我们可以设置它以便在最后一个读者离开时清理T
,但我的目标是正确性而不是回收T
的效率。
通过阅读器上的最小同步,您可以使用条件变量来表示阅读器已完成给定的T
;但这涉及到与作者线程的一点点争用。
实际上,无锁算法通常比基于锁的算法要慢,因为互斥体的开销并不像您担心的那么高。
一个 shared_timed_mutex
包装一个 shared_ptr
,作者只是覆盖了变量,这将非常快。现有读者将继续使用他们的旧shared_ptr
。
【讨论】:
以上是关于无锁重载和共享 const 对象的主要内容,如果未能解决你的问题,请参考以下文章
在非常量对象上,为啥 C++ 不调用具有 public-const 和 private-nonconst 重载的方法的 const 版本?
C++类和对象(构造函数析构函数拷贝构造函数赋值运算符重载Const成员)详细解读
C++类和对象(构造函数析构函数拷贝构造函数赋值运算符重载Const成员)详细解读