我的应用程序的配置是一个与多个线程共享的 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_ptr
读/写访问添加一个shared_mutex
,但我正在寻找一种可以保持实现无锁的解决方案。
编辑:我的GCC版本不支持atomic_exchange
shared_ptr
EDIT2:需求说明:我有多个阅读器,可能有多个重装器(尽管这种情况不太常见(。读者需要持有一个配置对象,并且在他们阅读它时它不会改变。当最后一个读取器完成旧配置对象处理完毕时,必须释放它们。
您应该只更新编译器以获取原子共享指针操作。
如果做不到这一点,请将其包裹在shared_timed_mutex
中。 然后测试它花了你多少钱。
正确编写自己的无锁共享指针系统相比,这两种方法都比工作量更少。
如果您必须:
这是一个黑客,但它可能会起作用。 它是指针本身的读取-复制-更新样式。
有一个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
。 当您想更改时,请在背面推一个新的。 保留此shared_ptr
的副本。
现在将"当前"指针换成新指针。 忙碌——等到active_readers
达到零,或者直到你感到无聊。
如果active_readers
达到零,请过滤vector
以查找使用计数为 1 的 shared_ptr
秒。 将它们从vector
中删除。
无论如何,现在将多余的shared_ptr
放到您创建的状态。 并完成了写作。
如果需要多个编写器,请使用单独的互斥锁锁定此进程。
在读卡器端,递增active_readers
。 现在原子加载"当前"指针,制作指向shared_ptr
的本地副本,然后递减active_readers
。
但是,我只是写了这个。 所以它可能包含错误。 证明并行设计的正确性是很困难的。
到目前为止,使其可靠的最简单方法是升级编译器并在shared_ptr上获取原子操作。
这可能过于复杂,我认为我们可以对其进行设置,以便在最后一个阅读器离开时清理T
,但我的目标是正确性而不是回收T
的效率。
通过读取器
上的最小同步,您可以使用条件变量来表示读取器已完成给定T
;但这涉及与编写器线程的少量争用。
实际上,无锁算法
通常比基于锁的算法慢,因为互斥锁的开销并不像你担心的那么高。
包装shared_ptr
的shared_timed_mutex
,编写者只是覆盖变量,这将是非常快的。 现有的读者将保持他们的旧shared_ptr
很好。