从并发析构函数中停止boost :: asio :: io_service :: run()

时间:2016-05-08 23:30:35

标签: c++ boost boost-asio

有人可以解释一下为什么这个程序没有终止(见评论)?

#include <boost/asio/io_service.hpp>
#include <boost/asio.hpp>
#include <memory>
#include <cstdio>
#include <iostream>
#include <future>

class Service {
public:
    ~Service() {
        std::cout << "Destroying...\n";
        io_service.post([this]() {
            std::cout << "clean and stop\n"; // does not get called
            // do some cleanup
            // ...
            io_service.stop();
            std::cout << "Bye!\n";
        });
        std::cout << "...destroyed\n"; // last printed line, blocks
    }

    void operator()() {
        io_service.run();
        std::cout << "run completed\n";
    }

private:
    boost::asio::io_service io_service;
    boost::asio::io_service::work work{io_service};
};

struct Test {
    void start() {
        f = std::async(std::launch::async, [this]() { service(); std::cout << "exiting thread\n";});
    }
    std::future<void> f;
    Service service;
};

int main(int argc, char* argv[]) {
    {
        Test test;
        test.start();

        std::string exit;
        std::cin >> exit;
    }

    std::cout << "exiting program\n"; // never printed
}

3 个答案:

答案 0 :(得分:2)

见这里:boost::asio hangs in resolver service destructor after throwing out of io_service::run()

我认为这里的诀窍是在调用work之前销毁工人(io_service.stop()成员)。即在这种情况下,work可以是unique_ptr,并在停止服务之前明确调用reset()

编辑:在我的案例中,上面帮助了我,ioservice::stop没有停止并等待一些从未发生过的调度事件。

但是我重现了你在我的机器上遇到的问题,这似乎是ioservice内的竞争条件,在ioservice::post()ioservice销毁代码之间的竞赛({{1} })。特别是,如果在shutdown_service通知唤醒另一个线程之前触发shutdown_service()post()代码将从队列中删除操作(&#34;销毁&#34;它而不是调用它,因此lambda从未被调用过。

现在在我看来,你需要直接在析构函数中调用shutdown_service(),而不是通过io_service.stop()推迟,因为因为比赛,这显然不适合在这里工作。

答案 1 :(得分:1)

真正的问题是int o=0; for (size_t i=0; i<(lss.size()+1); i++) { int k=o+1; x2=xdiff+matrix[o][0]; y2=ydiff+matrix[o][1]; z2=zdiff+matrix[o][2]; a2=xdiff+matrix[k][0]; b2=ydiff+matrix[k][1]; c2=zdiff+matrix[k][2]; fprintf(f, "X=%f, Y=%f, Z=%f \n", x2,y2,z2); fprintf(f, "X=%f, Y=%f, Z=%f \n", a2,b2,c2); o=o+2; } 的破坏(显然)不是线程安全的。

只需重置工作并加入线程即可。 (可选)设置一个标志,以便IO操作知道正在关闭。

您的测试和服务类正在尝试分担IO服务的责任,但这并不起作用。这里简化了很多,合并了类并放弃了未使用的未来。

<强> Live On Coliru

诀窍是制作io_service对象work

optional<>

打印

#include <boost/asio.hpp>
#include <boost/optional.hpp>
#include <iostream>
#include <thread>

struct Service {
    ~Service() {
        std::cout << "clean and stop\n";
        io_service.post([this]() {
            work.reset(); // let io_service run out of work
        });

        if (worker.joinable())
            worker.join();
    }

    void start() {
        assert(!worker.joinable());
        worker = std::thread([this] { io_service.run(); std::cout << "exiting thread\n";});
    }

private:
    boost::asio::io_service io_service;
    std::thread worker;
    boost::optional<boost::asio::io_service::work> work{io_service};
};

int main() {
    {
        Service test;
        test.start();

        std::cin.ignore(1024, '\n');
        std::cout << "Start shutdown\n";
    }

    std::cout << "exiting program\n"; // never printed
}

答案 2 :(得分:-1)

我能够通过重写代码来解决问题:

class Service {
public:
    ~Service() {
        std::cout << "Destroying...\n";
        work.reset();
        std::cout << "...destroyed\n"; // last printed line, blocks
    }

    void operator()() {
        io_service.run();
        std::cout << "run completed\n";
    }

private:
    boost::asio::io_service io_service;
    std::unique_ptr<boost::asio::io_service::work> work = std::make_unique<boost::asio::io_service::work>(io_service);
};

然而,这主要是一种极限解决方案。

问题在于你的设计理念;特别是,选择不将执行线程的生命周期直接绑定到io_service对象:

struct Test {
    void start() {
        f = std::async(std::launch::async, [this]() { service(); std::cout << "exiting thread\n";});
    }
    std::future<void> f; //Constructed First, deleted last
    Service service; //Constructed second, deleted first
};

在此特定方案中,线程将继续尝试在io_service.run()对象本身的生命周期之后执行io_service。如果在服务上执行了超过基本work对象,则很快就会开始使用已删除对象的成员函数来处理未定义的行为。

您可以在Test中反转成员对象的顺序:

struct Test {
    void start() {
        f = std::async(std::launch::async, [this]() { service(); std::cout << "exiting thread\n";});
    }
    Service service;
    std::future<void> f;
};

但它仍然是一个重大的设计缺陷。

我通常实现使用io_service的任何东西的方法是将其生命周期与实际将要在其上执行的线程联系起来。

class Service {
public:
    Service(size_t num_of_threads = 1) :
        work(std::make_unique<boost::asio::io_service::work>(io_service))
    {
        for (size_t thread_index = 0; thread_index < num_of_threads; thread_index++) {
            threads.emplace_back([this] {io_service.run(); });
        }
    }

    ~Service() {
        work.reset();
        for (std::thread & thread : threads) 
            thread.join();
    }
private:
    boost::asio::io_service io_service;
    std::unique_ptr<boost::asio::io_service::work> work;
    std::vector<std::thread> threads;
};

现在,如果你在任何这些线程上有任何无限循环活动,你仍然需要确保正确清理它们,但至少要清除特定于此io_service操作的代码正确的。