如何避免在不适当的线程上下文中销毁shared_ptr?

时间:2016-04-13 10:08:22

标签: c++ c++11 boost boost-asio shared-ptr

这是一个在我的应用程序中重现问题的相对简单的程序:

#include <boost/asio.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/noncopyable.hpp>
#include <boost/thread.hpp>
#include <chrono>
#include <functional>
#include <iostream>
#include <memory>

class worker : boost::noncopyable {
public:
    explicit worker(boost::asio::io_service& io);
    ~worker();
    void just_do_it(const std::function<void()>& when_done);

private:
    boost::asio::io_service& io_;
    boost::asio::io_service worker_io_;
    boost::thread thread_;
};

worker::worker(boost::asio::io_service& io)
: io_(io)
{
    thread_ = boost::thread([this] {
        boost::asio::io_service::work my_work(worker_io_);
        worker_io_.run();
    });
}

worker::~worker()
{
    worker_io_.stop();
    std::clog << "join...\n";
    thread_.join();
}

void worker::just_do_it(const std::function<void()>& when_done)
{
    worker_io_.post([this, when_done] {
        io_.post(when_done);
        boost::asio::steady_timer(worker_io_, std::chrono::seconds(1)).wait();
    });
}

int main()
{
    boost::asio::io_service io;
    boost::asio::steady_timer timer(io, std::chrono::seconds(5));
    timer.async_wait(std::bind([] { std::clog << "terminating...\n"; }));
    {
        auto my_worker = std::make_shared<worker>(io);
        my_worker->just_do_it([my_worker] {
            std::clog << "did it\n";
            my_worker->just_do_it([my_worker] {
                std::clog << "did it second time\n";
                // now my_worker is not needed and we allow it to die
            });
        });
    }
    io.run();
}

当我在Linux机器上运行时,我看到:

did it
did it second time
join...
terminate called after throwing an instance of 'boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::thread_resource_error> >'
  what():  boost thread: trying joining itself: Resource deadlock avoided
Aborted

由于shared_ptr在worker`s thread中调用worker析构函数,因此崩溃了。我可以这样解决:

std::shared_ptr<worker> holder;
{
    holder = std::make_shared<worker>(io);
    holder->just_do_it([&holder] {
        std::clog << "did it\n";
        holder->just_do_it([&holder] {
            std::clog << "did it second time\n";
            // now worker is not needed and we destroy it
            holder.reset();
        });
    });
}

但它是一个手动对象生命周期管理。它并不比使用new和delete好多少。有什么方法可以避免吗?

1 个答案:

答案 0 :(得分:1)

thread对象应该由管理线程生命周期的代码所拥有。

目前,您的thread生命周期由您的worker类管理,您的worker类的生命周期由执行线程管理。

这是一个非常基本的管理循环。

工人破坏的语义也非常混乱。你有共享指针,当最后一个共享指针被销毁时,某个线程上的操作阻塞完成了一些未知的任务。在该系统下,预测哪些参考计数操作几乎不可能。

一种方法是捕捉工人内部工人的弱点。然后负责管理工作线程生命周期的代码在它们全部结束时进行选择;当共享指针消失时。

线程通常需要由外部实体管理。

可能有用的方法是具有命名任务队列的线程池。每个任务队列都保证按顺序运行,因此它就像一个线程,但是不能保证队列中的任务将在同一个线程上运行,也不保证线程将空闲等待队列中的新任务。

您可以使用类似guid的命名来命名队列,也可以使用根据请求生成的全局唯一标识符(如某种类型的new'd指针)。在第二种情况下,等同于.join()调用正在处理您的线程队列ID。

这确实可以让您远离使用原始boost线程原语;但根据我的经验,std - 样式原语是使用pthreads样式线程的一个步骤,但仍然远不是你真正想在客户端代码中直接使用的。