很遗憾,Boost ASIO 没有async_wait_for_condvar() 方法。
在大多数情况下,您也不需要它。以 ASIO 方式编程通常意味着您使用链而不是互斥锁或条件变量来保护共享资源。除了在启动和退出时通常关注正确的构造或销毁顺序的极少数情况外,您根本不需要互斥锁或条件变量。
修改共享资源时,经典的部分同步线程方式如下:
- 锁定保护资源的互斥锁
- 更新需要更新的内容
- 如果需要等待线程进一步处理,则向条件变量发出信号
- 解锁互斥锁
完全异步的 ASIO 方式是:
- 生成一条消息,其中包含更新资源所需的所有内容
- 将带有该消息的更新处理程序调用发布到资源链
- 如果需要进一步处理,让更新处理程序创建更多消息并将它们发布到适当的资源链。
- 如果作业可以在完全私有的数据上执行,则将它们直接发布到 io-context。
这是一个类some_shared_resource 的示例,它接收一个字符串state 并根据接收到的状态触发一些进一步的处理。请注意,私有方法 some_shared_resource::receive_state() 中的所有处理都是完全线程安全的,因为 strand 会序列化所有调用。
当然,这个例子是不完整的; some_other_resource 需要与some_shared_ressource::send_state() 类似的send_code_red() 方法。
#include <boost/asio>
#include <memory>
using asio_context = boost::asio::io_context;
using asio_executor_type = asio_context::executor_type;
using asio_strand = boost::asio::strand<asio_executor_type>;
class some_other_resource;
class some_shared_resource : public std::enable_shared_from_this<some_shared_resource> {
asio_strand strand;
std::shared_ptr<some_other_resource> other;
std::string state;
void receive_state(std::string&& new_state) {
std::string oldstate = std::exchange(state, new_state);
if(state == "red" && oldstate != "red") {
// state transition to "red":
other.send_code_red(true);
} else if(state != "red" && oldstate == "red") {
// state transition from "red":
other.send_code_red(false);
}
}
public:
some_shared_resource(asio_context& ctx, const std::shared_ptr<some_other_resource>& other)
: strand(ctx.get_executor()), other(other) {}
void send_state(std::string&& new_state) {
boost::asio::post(strand, [me = weak_from_this(), new_state = std::move(new_state)]() mutable {
if(auto self = me.lock(); self) {
self->receive_state(std::move(new_state));
}
});
}
};
如您所见,一开始总是向 ASIO 发布帖子可能有点乏味。但是您可以将大部分“为类配备链”代码移动到模板中。
消息传递的好处:由于您不使用互斥锁,因此即使在极端情况下,您也不会再陷入僵局。此外,使用消息传递,创建高级并行性通常比使用经典多线程更容易。不利的一面是,围绕所有这些消息对象移动和复制非常耗时,这会降低您的应用程序的速度。
最后一点:在send_state()形成的消息中使用弱指针有助于可靠地销毁some_shared_resource对象:否则,如果A调用B,B调用C,C调用A(可能仅在超时或类似),在消息中使用共享指针而不是弱指针会创建循环引用,从而防止对象破坏。如果您确定永远不会有循环,并且处理来自待删除对象的消息不会造成问题,那么您当然可以使用shared_from_this() 而不是weak_from_this()。如果您确定在 ASIO 停止之前不会删除对象(并且所有工作线程都已加入主线程),那么您也可以直接捕获 this 指针。