【问题标题】:Cancelling boost asio deadline timer safely安全地取消 boost asio 截止时间计时器
【发布时间】:2017-08-27 08:23:43
【问题描述】:

我正在尝试安全地取消boost::asio::basic_waitable_timer<std::chrono::steady_clock>

根据answer,这段代码应该可以完成这项工作:

timer.get_io_service().post([&]{timer.cancel();})

恐怕它不适合我。
我是不是做错了什么?
这是我的代码:

#include <iostream>
#include "boost/asio.hpp"
#include <chrono>
#include <thread>
#include <random>

boost::asio::io_service io_service;
boost::asio::basic_waitable_timer<std::chrono::steady_clock> timer(io_service);
std::atomic<bool> started;

void handle_timeout(const boost::system::error_code& ec)
{
    if (!ec) {
        started = true;
        std::cerr << "tid: " << std::this_thread::get_id() << ", handle_timeout\n";
        timer.expires_from_now(std::chrono::milliseconds(10));
        timer.async_wait(&handle_timeout);
    } else if (ec == boost::asio::error::operation_aborted) {
        std::cerr << "tid: " << std::this_thread::get_id() << ", handle_timeout aborted\n";
    } else {
        std::cerr << "tid: " << std::this_thread::get_id() << ", handle_timeout another error\n";
    }
}

int main() {

    std::cout << "tid: " << std::this_thread::get_id() << ", Hello, World!" << std::endl;
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(1, 100);

    for (auto i = 0; i < 1000; i++) {

        started = false;
        std::thread t([&](){

            timer.expires_from_now(std::chrono::milliseconds(0));
            timer.async_wait(&handle_timeout);

            io_service.run();
        });

        while (!started) {};
        auto sleep = dis(gen);
        std::cout << "tid: " << std::this_thread::get_id() << ", i: " << i << ", sleeps for " << sleep << " [ms]" << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(sleep));
        timer.get_io_service().post([](){
            std::cerr << "tid: " << std::this_thread::get_id() << ", cancelling in post\n";
            timer.cancel();
        });
//      timer.cancel();
        std::cout << "tid: " << std::this_thread::get_id() << ", i: " << i << ", waiting for thread to join()" << std::endl;
        t.join();
        io_service.reset();
    }

    return 0;
}

这是输出:

...
时间:140737335076608,handle_timeout
时间:140737335076608,handle_timeout
tid: 140737353967488, i: 2, 等待线程加入()
tid: 140737335076608, 后期取消
tid:140737335076608,handle_timeout 中止
tid: 140737353967488, i: 3, 休眠 21 [ms]
时间:140737335076608,handle_timeout
tid: 140737353967488, i: 3, 等待线程加入()
时间:140737335076608,handle_timeout
tid: 140737335076608, 后期取消
时间:140737335076608,handle_timeout
时间:140737335076608,handle_timeout
时间:140737335076608,handle_timeout
时间:140737335076608,handle_timeout
时间:140737335076608,handle_timeout
...
永远继续……

如您所见,timer.cancel() 正在从相应的线程调用:

tid: 140737335076608,在帖子中取消

但是没有

tid: 140737335076608,handle_timeout 中止

之后。

Main 永远等待。

【问题讨论】:

    标签: c++ multithreading boost timer boost-asio


    【解决方案1】:

    取消安全的。

    它只是不健壮。您没有考虑计时器未挂起的情况。然后,您取消它一次,但一旦调用完成处理程序,它就会开始一个新的异步等待。

    以下是我如何追踪问题的详细步骤。

    总结 TL;DR

    取消时间只会取消运行中的异步操作。

    如果要关闭异步调用链,则必须为此使用额外的逻辑。下面给出一个例子。

    处理程序跟踪

    启用

    #define BOOST_ASIO_ENABLE_HANDLER_TRACKING 1
    

    这会产生可以用boost/libs/asio/tools/handlerviz.pl 可视化的输出:

    成功的跟踪

    如您所见,当取消发生时,async_wait 正在运行中。

    “坏”的痕迹

    (被截断,因为它会无限运行)

    注意完成处理程序如何看到cc=system:0,而不是cc=system:125(对于operation_aborted)。这是发布的取消实际上并未“接受”这一事实的症状。唯一合乎逻辑的解释(图中不可见)是在调用取消之前计时器已经到期。

    让我们比较原始痕迹¹

    ¹ 消除噪音差异

    检测它

    所以,我们有领先优势。我们能检测到吗?

        timer.get_io_service().post([](){
            std::cerr << "tid: " << std::this_thread::get_id() << ", cancelling in post\n";
            if (timer.expires_from_now() >= std::chrono::steady_clock::duration(0)) {
                timer.cancel();
            } else {
                std::cout << "PANIC\n";
                timer.cancel();
            }
        });
    

    打印:

    tid: 140113177143232, i: 0, waiting for thread to join()
    tid: 140113177143232, i: 1, waiting for thread to join()
    tid: 140113177143232, i: 2, waiting for thread to join()
    tid: 140113177143232, i: 3, waiting for thread to join()
    tid: 140113177143232, i: 4, waiting for thread to join()
    tid: 140113177143232, i: 5, waiting for thread to join()
    tid: 140113177143232, i: 6, waiting for thread to join()
    tid: 140113177143232, i: 7, waiting for thread to join()
    tid: 140113177143232, i: 8, waiting for thread to join()
    tid: 140113177143232, i: 9, waiting for thread to join()
    tid: 140113177143232, i: 10, waiting for thread to join()
    tid: 140113177143232, i: 11, waiting for thread to join()
    tid: 140113177143232, i: 12, waiting for thread to join()
    tid: 140113177143232, i: 13, waiting for thread to join()
    tid: 140113177143232, i: 14, waiting for thread to join()
    tid: 140113177143232, i: 15, waiting for thread to join()
    tid: 140113177143232, i: 16, waiting for thread to join()
    tid: 140113177143232, i: 17, waiting for thread to join()
    tid: 140113177143232, i: 18, waiting for thread to join()
    tid: 140113177143232, i: 19, waiting for thread to join()
    tid: 140113177143232, i: 20, waiting for thread to join()
    tid: 140113177143232, i: 21, waiting for thread to join()
    tid: 140113177143232, i: 22, waiting for thread to join()
    tid: 140113177143232, i: 23, waiting for thread to join()
    tid: 140113177143232, i: 24, waiting for thread to join()
    tid: 140113177143232, i: 25, waiting for thread to join()
    tid: 140113177143232, i: 26, waiting for thread to join()
    PANIC
    

    我们能否以另一种更清晰的方式传达“超级取消”?当然,我们只有 timer 对象可以使用:

    信号关闭

    timer 对象没有很多可使用的属性。没有close() 或类似的东西,比如在套接字上,可用于将计时器置于某种无效状态。

    但是,有到期时间点,我们可以使用一个特殊的域 为我们的应用程序发出“无效”信号的值:

    timer.get_io_service().post([](){
        std::cerr << "tid: " << std::this_thread::get_id() << ", cancelling in post\n";
        // also cancels:
        timer.expires_at(Timer::clock_type::time_point::min());
    });
    

    这个“特殊值”在完成处理程序中很容易处理:

    void handle_timeout(const boost::system::error_code& ec)
    {
        if (!ec) {
            started = true;
            if (timer.expires_at() != Timer::time_point::min()) {
                timer.expires_from_now(std::chrono::milliseconds(10));
                timer.async_wait(&handle_timeout);
            } else {
                std::cerr << "handle_timeout: detected shutdown\n";
            }
        } 
        else if (ec != boost::asio::error::operation_aborted) {
            std::cerr << "tid: " << std::this_thread::get_id() << ", handle_timeout error " << ec.message() << "\n";
        }
    }
    

    【讨论】:

    • 哇,谢谢!你写了The cancellation is safe. - 你的意思是使用post(),对吧?不一般timer.cancel()
    • 不错的解决方法,但是...您不认为应该有更好的取消功能来隐藏其实现细节中的所有这些混乱吗?与取消有关的问题一次又一次地出现......
    • @hudac 我只是确认您对它的使用是线程安全的,我实际上并没有说别的。您对它的使用是安全的因为您将它发布到服务并且该服务在单个线程上运行,这意味着您获得“隐式链”行为(没有两个处理程序运行过同时)。
    • @hudac 更具体地说,一旦您在更多线程上运行服务,这不是经验法则!在这种情况下,您需要一个链来同步对服务对象的访问(例如deadline_timer)。见stackoverflow.com/questions/12794107/…。我希望这能说明cancel() 不是线程安全的观点,根据文档(没有人这么说)。
    • @hudac 我不认为我这样做(我通常只听一次 INT/TERM)。您当然可以简单地signal_set.clear(...);(现在当您收到信号 0 时,这意味着您可能应该关机)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多