我正在寻找在Boost.ASIO中使用容器数据结构时应用的一些指导原则。 Boost.ASIO文档描述了如何使用strand
对象来提供对共享资源的序列化访问,而无需显式线程同步。我正在寻找一种将strand
同步应用于:
std::deque
,std::unordered_map
);和boost::lockfree::spsc_queue
或folly::ProducerConsumerQueue
。 我的问题如下。我应该提到我的主要问题是1-3,但有理由我也准备接受“这些问题没有实际意义/误导”作为答案;我在问题4中详细说明了这一点。
要使任意STL容器适应安全同步使用,通过strand
实例执行所有操作是否足够?
为了使一个无等待的读写容器适应同步,并发使用,它是否足以通过两个不同的strand
来包装它们,一个用于读操作,一个用于写操作? This question提示“是”,尽管在该用例中,作者描述了使用strand
来协调来自多个线程的生产者,而大概只能从一个线程中读取。
如果上面1-2的回答是肯定的,那么strand
是否应通过调用boost::asio::post
来管理数据结构上的操作?
要了解3.中的问题,请参阅chat client example的摘要:
void write(const chat_message& msg)
{
boost::asio::post(io_context_,
[this, msg]()
{
bool write_in_progress = !write_msgs_.empty();
write_msgs_.push_back(msg);
if (!write_in_progress)
{
do_write();
}
});
}
此处,write_msgs_
是聊天消息的队列。我问,因为这是一个特殊情况,对post
的调用可能会调用组合的异步操作(do_write
)。如果我只是想从队列中推送或弹出怎么办?举一个高度简化的例子:
template<typename T>
class MyDeque {
public:
push_back(const T& t);
/* ... */
private:
std::deque<T> _deque;
boost::asio::io_context::strand _strand
};
然后应MyDeque::push_back(const T& t)
致电
boost::asio::post(_strand, [&_deque]{ _deque.push_back(t); })
和其他操作类似?或者boost::asio::dispatch
是更合适的选择吗?
答案 0 :(得分:2)
我正在寻找一种将链同步应用于:
的系统方法
- STL(或STL类)容器(例如,std :: deque,std :: unordered_map);和
你正在寻找一些不存在的东西。最接近的是Active Objects,除非你对它们进行异步操作,否则你几乎不需要它。这几乎没有意义,因为STL容器上的操作不应该具有足够的时间复杂度以保证异步。另一方面,计算复杂性使得添加任何类型的同步都是非常不理想的 -
而不是细粒度锁定[在将STL数据结构作为ActiveObjects时自动选择],您将始终通过粗粒度锁定找到更好的性能。
在设计中,通过减少共享,您将始终获得更多性能,而不是优化同步&#34; (那些是矛盾的。)
- 无等待容器,例如boost :: lockfree :: spsc_queue或folly :: ProducerConsumerQueue。
为什么你甚至会同步对等待容器的访问。等待免费意味着没有同步。
要使任意STL容器适应安全同步使用,是否足以通过strand实例执行所有操作?
是。只有那不存在。 Strands包装异步任务(及其完成处理程序,它们只是执行程序的POV中的任务)。
见上面的咆哮。
为了使同步,并发使用的等待读写容器适应,是否足以通过两个不同的链包装其操作,一个用于读操作,一个用于写操作?
如上所述,同步访问无锁结构是很愚蠢的。
这个问题暗示了一个&#34; yes&#34;,尽管在该用例中,作者描述了使用一个strand来协调来自多个线程的生成器,而大概只能从一个线程读取。
具体 与SPSC队列相关,即对执行读/写操作的线程施加额外约束。
虽然这里的解决方案确实是通过对任一组操作的独占访问来创建逻辑执行线程,通知您正在约束任务,这是根本不同的约束数据的角度。
如果上面1-2的答案是肯定的,那么该链是否应该通过调用boost :: asio :: post来管理数据结构上的操作?
所以,答案不是&#34;是&#34;。通过post
发布所有操作的想法将归结为实现Active Object模式,如我的介绍中所述。是的,你可以做到这一点,不,那不会是聪明的。 (我很确定,如果你这样做,根据定义你可以忘记使用无锁容器)
[...]
那么MyDeque :: push_back(const T&amp; t)应该调用boost::asio::post(_strand, [&_deque]{ _deque.push_back(t); })
是的,这是ActiveObject模式。但是,请考虑如何实施top()
。如果您有两个MyDeque
个实例(a
和`b)并想要将项目从一个移动到另一个,请考虑您要做的事情:
if (!a.empty()) {
auto value = a.top(); // synchronizes on the strand to have the return value
a.pop(); // operation on the strand of a
b.push(std::move(value)); // operation on the strand of b
}
由于队列b
不在a
的链上,b.push()
实际上可能会在a.pop()
之前提交,这可能不是您所期望的。此外,它显而易见的是,所有细粒度的同步步骤的效率远远低于对一组数据结构进行处理的所有操作的链。
[...]但似乎完全并发的向量或哈希映射的力量可能有点矫枉过正
没有&#34; force&#34;在完全并发的向量或哈希映射中。它们的成本(在处理器业务方面)和增益(在较低的延迟方面)。在你提到的情况下,延迟很少是问题(注册会话是一个不常见的事件,并由实际的IO速度支配),所以你最好使用最简单的东西(IMO将是单线程服务器对于那些数据结构)。让工作人员进行任何非平凡的操作 - 他们可以在一个线程池上运行。 (例如,如果您决定实施国际象棋游戏机器人)
您希望strands形成执行的逻辑线程。您希望将访问同步到数据结构,而不是数据结构本身。有时无锁数据结构是一个简单的选择,可以避免设计好,但不要期望它神奇地表现良好。
一些链接: