c++ - 如何避免在不适当的线程上下文中破坏 shared_ptr?

标签 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 在工作线程中调用了工作析构函数。我可以这样修复它:

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 好太多。有什么办法可以避免吗?

最佳答案

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

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

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

worker 销毁的语义也非常困惑。你有共享指针,当最后一个共享指针被销毁时,操作 block 在某个线程上某处完成一些未知任务。在这个系统下,预测什么引用计数操作阻塞变得几乎不可能。


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


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

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

您可以使用诸如 guid 之类的东西来命名队列,或者根据请求生成一个不太全局唯一的标识符(比如某种 new 指针)。在第二种情况下,您的 .join() 调用相当于处理您的线程队列 ID。

这确实使您远离使用原始 boost 线程原语;但根据我的经验,std 风格的原语比使用 pthreads 风格的线程更上一层楼,但距离您实际想要直接在客户端代码中使用的内容还差得很远。

关于c++ - 如何避免在不适当的线程上下文中破坏 shared_ptr?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36595162/

相关文章:

c++ - Boost 文件系统非常慢?

C++ 多方法和编译时检测

c++ - 将别名模板传递给它所依赖的模板的基类

c++ - 我如何以无序和可变的方式使用 boost::bimap?

c++ - 如何从 cin 获取用户输入到 C++11 std::array

c++ - 为什么在上下文转换中不发生显式 bool() 转换

c++ - 如果我知道 gdb 的类型,我怎么能看到 boost::any 的值

c++ - apache thrift C++ 服务器客户端连接超时

c++ - 剪贴板快捷键/热键在应用程序外与 Qt 绑定(bind)

c++ - 终端中光标闪烁的删除,如何?