首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >boost::asio::bind_executor不会在链中执行

boost::asio::bind_executor不会在链中执行
EN

Stack Overflow用户
提问于 2022-03-15 00:03:10
回答 1查看 918关注 0票数 2

以下示例在没有断言的情况下完成:

代码语言:javascript
运行
复制
#include <cassert>
#include <functional>
#include <future>
#include <thread>
#include <boost/asio.hpp>

class example1
{
public:
    typedef boost::asio::io_context io_context;
    typedef boost::asio::io_context::executor_type executor_type;
    typedef boost::asio::strand<executor_type> strand;
    typedef boost::asio::executor_work_guard<executor_type> work_guard;
    typedef std::function<void()> handler;

    example1()
      : work_(boost::asio::make_work_guard(context_)),
        thread_([this]() { context_.run(); }),
        strand1_(context_.get_executor()),
        strand2_(context_.get_executor())
    {

    }

    ~example1()
    {
        assert(result_.get_future().get());
        work_.reset();
        thread_.join();
    }

    void invoke()
    {
        handler handle = boost::asio::bind_executor(strand2_,
            std::bind(&example1::strand2_handler, this));

        boost::asio::post(strand1_,
            std::bind(&example1::strand1_handler, this, handle));
    }

    void strand1_handler(handler handle)
    {
        assert(strand1_.running_in_this_thread());
        handle();
    }

    void strand2_handler()
    {
        assert(strand1_.running_in_this_thread());
        ////assert(strand2_.running_in_this_thread());
        result_.set_value(true);
    }

private:
    io_context context_;
    work_guard work_;
    std::thread thread_;
    strand strand1_;
    strand strand2_;
    std::promise<bool> result_;
};

int main()
{
    example1 test{};
    test.invoke();
}

然而,我的期望是,注释掉的断言应该成功,而不是直接高于它的断言。根据strand::running_in_this_thread(),在调用方的链中调用了处理程序handle,而不是提供给bind_executor的处理程序。

我可以使用中间方法来解决这个问题,如下所示。

代码语言:javascript
运行
复制
class example2
{
public:
    typedef boost::asio::io_context io_context;
    typedef boost::asio::io_context::executor_type executor_type;
    typedef boost::asio::strand<executor_type> strand;
    typedef boost::asio::executor_work_guard<executor_type> work_guard;
    typedef std::function<void()> handler;

    example2()
      : work_(boost::asio::make_work_guard(context_)),
        thread_([this]() { context_.run(); }),
        strand1_(context_.get_executor()),
        strand2_(context_.get_executor())
    {

    }

    ~example2()
    {
        assert(result_.get_future().get());
        work_.reset();
        thread_.join();
    }

    void invoke()
    {
        handler handle =
            std::bind(&example2::do_strand2_handler, this);

        boost::asio::post(strand1_,
            std::bind(&example2::strand1_handler, this, handle));
    }

    void strand1_handler(handler handle)
    {
        assert(strand1_.running_in_this_thread());
        handle();
    }

    // Do the job of bind_executor.
    void do_strand2_handler()
    {
        boost::asio::post(strand2_,
            std::bind(&example2::strand2_handler, this));
    }

    void strand2_handler()
    {
        ////assert(strand1_.running_in_this_thread());
        assert(strand2_.running_in_this_thread());
        result_.set_value(true);
    }

private:
    io_context context_;
    work_guard work_;
    std::thread thread_;
    strand strand1_;
    strand strand2_;
    std::promise<bool> result_;
};

int main()
{
    example2 test2{};
    test2.invoke();
}

但是,避免这种情况可能是bind_executor的目的。这是助推虫还是我漏掉了什么?我尝试通过boost::asio源代码来跟踪这一点,但没有结果。

更新

感谢@sehe给了我很多帮助。可以通过多种方式解决上述问题,例如:

代码语言:javascript
运行
复制
class example3
{
public:
    typedef boost::asio::io_context io_context;
    typedef boost::asio::io_context::executor_type executor_type;
    typedef boost::asio::strand<executor_type> strand;
    typedef boost::asio::executor_work_guard<executor_type> work_guard;
    typedef boost::asio::executor_binder<std::function<void()>,
        boost::asio::any_io_executor> handler;

    example3()
      : work_(boost::asio::make_work_guard(context_)),
        thread_([this]() { context_.run(); }),
        strand1_(context_.get_executor()),
        strand2_(context_.get_executor())
    {
    }

    ~example3()
    {
        assert(result_.get_future().get());
        work_.reset();
        thread_.join();
    }

    void invoke()
    {
        auto handle = boost::asio::bind_executor(strand2_,
            std::bind(&example3::strand2_handler, this));

        boost::asio::post(strand1_,
            std::bind(&example3::strand1_handler, this, handle));
    }

    void strand1_handler(handler handle)
    {
        assert(strand1_.running_in_this_thread());
        boost::asio::dispatch(handle);
    }

    void strand2_handler()
    {
        assert(strand2_.running_in_this_thread());
        result_.set_value(true);
    }

private:
    io_context context_;
    work_guard work_;
    std::thread thread_;
    strand strand1_;
    strand strand2_;
    std::promise<bool> result_;
};

int main
{
    example3 test3{};
    test3.invoke();
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-03-15 01:05:22

是的,你确实错过了什么。实际上有两件事。

类型擦除

绑定执行器不会修改函数,而是修改其类型。

但是,通过使用std::function<>擦除可调用的类型,您已经隐藏了绑定执行器。您可以很容易地确定这一点:

代码语言:javascript
运行
复制
erased_handler handle = bind_executor(s2, s2_handler);
assert(asio::get_associated_executor(handle, s1) == s1);

当您保留以下类型时,问题就消失了:

代码语言:javascript
运行
复制
auto handle = bind_executor(s2, s2_handler);
assert(asio::get_associated_executor(handle, s1) == s2);

派遣(前称handler_invoke)

调用handle直接根据C++语言语义调用它,正如您已经发现的那样。

要要求Asio履行可能绑定的执行器,可以使用dispatch (或post):

代码语言:javascript
运行
复制
auto s1_handler = [&](auto chain) {
    assert(s1.running_in_this_thread());
    dispatch(get_associated_executor(chain, s1), chain);
};

实际上,如果您确信将有一个关联的执行器,则可以接受默认的回退(即系统执行器):

代码语言:javascript
运行
复制
auto s1_handler = [&](auto chain) {
    assert(s1.running_in_this_thread());
    dispatch(chain);
};

把所有的东西都放在一起

在一个简化的、扩展的测试器中展示智慧:

住在Coliru

代码语言:javascript
运行
复制
#include <boost/asio.hpp>
#include <functional>
#include <iostream>

namespace asio = boost::asio;

int main() {
    asio::thread_pool io(1);

    auto s1 = make_strand(io), s2 = make_strand(io);
    assert(s1 != s2); // implementation defined! strands may hash equal

    auto s1_handler = [&](auto chain) {
        assert(s1.running_in_this_thread());

        // immediate invocation runs on the current strand:
        chain();

        // dispatch *might* invoke directly if already on the right strand
        dispatch(chain);                                     // 1
        dispatch(get_associated_executor(chain, s1), chain); // 2

        // posting never immediately invokes, even if already on the right
        // strand
        post(chain);                                     // 3
        post(get_associated_executor(chain, s1), chain); // 4
    };

    int count_chain_invocations = 0;
    auto s2_handler = [&] {
        if (s2.running_in_this_thread()) {
            count_chain_invocations += 1;
        } else {
            std::cout << "(note: direct C++ call ends up on wrong strand)\n";
        }
    };

    {
        using erased_handler  = std::function<void()>;
        erased_handler handle = bind_executor(s2, s2_handler);
        assert(asio::get_associated_executor(handle, s1) == s1);
    }
    {
        auto handle = bind_executor(s2, s2_handler);
        assert(asio::get_associated_executor(handle, s1) == s2);
    }

    auto handle = bind_executor(s2, s2_handler);
    post(s1, std::bind(s1_handler, handle));

    io.join();

    std::cout << "count_chain_invocations: " << count_chain_invocations << "\n";
}

所有断言都会传递,输出如预期的那样:

代码语言:javascript
运行
复制
(note: direct C++ call ends up on wrong strand)
count_chain_invocations: 4

奖金:如果你需要类型擦除绑定卡莱士呢?

不管你做什么,都不要使用std::function。不过,你可以包一个;

代码语言:javascript
运行
复制
template <typename Sig> struct ErasedHandler {
    using executor_type = asio::any_io_executor;
    std::function<Sig> _erased;
    executor_type      _ex;
    executor_type get_executor() const { return _ex; }

    template <typename F>
    explicit ErasedHandler(F&& f)
        : _erased(std::forward<F>(f))
        , _ex(asio::get_associated_executor(f)) {}

    ErasedHandler() = default;

    template <typename... Args>
    decltype(auto) operator()(Args&&... args) const {
        return _erased(std::forward<Args>(args)...);
    }
    template <typename... Args>
    decltype(auto) operator()(Args&&... args) {
        return _erased(std::forward<Args>(args)...);
    }

    explicit operator bool() const { return _erased; }
};

看吧,住在Coliru

在此之前,请注意

  • 使用any_io_executor还可以擦除执行器,这可能会损害性能。
  • 它没有提供一个好的后盾,只是使用系统执行器为未绑定的可调用。您可以通过检测它和要求显式构造函数引用等来解决这个问题,但是.
  • 所有这些仍然完全忽略其他处理程序属性,如关联分配器。

我可能会避免一般地存储类型擦除的链式处理程序。您通常可以存储由模板类型参数导出的处理程序的实际类型。

PS:后遗症

也许你所期待的是这种行为:

代码语言:javascript
运行
复制
template <typename... Args>
decltype(auto) operator()(Args&&... args) const {
    // CAUTION: NOT WHAT YOU WANT
    boost::asio::dispatch(_ex,
                          std::bind(_erased, std::forward<Args>(args)...));
}
template <typename... Args>
decltype(auto) operator()(Args&&... args) {
    // CAUTION: NOT WHAT YOU WANT
    boost::asio::dispatch(_ex,
                          std::bind(_erased, std::forward<Args>(args)...));
}

看到住在Coliru

在这个方案下,即使是直接的C++调用也会“做正确的事情”。

听起来不错。在你考虑之前。

问题是,处理程序不能以这种方式反弹。更具体地说,如果您有一个与“免费线程”执行器相关联的处理程序,那么执行bind_executor(strand, f)将不会产生任何效果(除了减慢您的程序),因为无论如何,f都会被强制分配给另一个执行器。

所以不要这样做:)

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71475533

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档