在另一个线程上安全地释放资源

时间:2015-09-29 21:58:35

标签: c++ multithreading c++11

我有一个类似的课程:

class A{
    private:
    boost::shared_ptr< Foo > m_pFoo;
}

在GUI线程上销毁A的实例,其中可能保持对Foo的最后一个引用。 Foo的析构函数可能长时间运行,导致我的GUI线程出现意外暂停。 在这种情况下,我想让Foo在一个单独的线程上被销毁,Foo是自包含的并且它并不重要 他们立即被释放。

目前,我使用这样的模式:

A::~A(){
    auto pMtx = boost::make_shared<boost::mutex>();
    boost::unique_lock<boost::mutex> destroyerGate(*pMtx);
    auto pFoo = m_pFoo;
    auto destroyer = [pMtx,pFoo](){
        boost::unique_lock<boost::mutex> gate(*pMtx);
    };

    m_pFoo.reset();
    pFoo.reset();
    s_cleanupThread->post(destroyer);
}

基本上,在lambda中捕获它并锁定直到从对象释放。有没有更好的方法来实现这一目标?这似乎比它需要的更复杂。

2 个答案:

答案 0 :(得分:0)

A不应对m_pFoo目标的破坏负责。 shared_ptr shared_ptr点所负责的资源的破坏是~A的责任,因此在我看来,你不应该微观管理真实对象在{{1}内发生破坏的线程。 }}。

不是实现一种适合您需求的新型智能指针,我认为这里的一个很好的折衷方案是从~A中删除与底层对象相关的逻辑并将其移动到自定义删除器在构造时提供给shared_ptr的。如果您对目前的解除分配策略感到满意,我认为这是一种令人满意的方法。但我同意其他人的意见,你可能想要调查那些不涉及为每次重新分配创建新线程的策略。

您可以找到有关如何向smart_ptr here提供删除的文档。向下滚动到'带删除器的构造函数'(您也可以查找有关正在使用的特定版本的boost的文档)。

答案 1 :(得分:0)

正如Mark Ransom已在评论中建议的那样,您可以使用专用的销毁线程,该线程从工作队列中获取待销毁的对象,然后将它们放在地板上。这可以假设,如果你离开一个物体,破坏移走的物体将是非常便宜的。

我在这里提出一个destruction_service课程,对你希望它销毁的对象类型进行模板化。这可以是任何类型的对象,而不仅仅是共享指针。实际上,共享指针甚至是最棘手的,因为您必须小心,如果引用计数已达到1,则只提交std::shared_ptr进行销毁。否则,破坏销毁线程上的std::shared_ptr基本上是无操作,除了减少引用计数。但是,在这种情况下,没有什么事情会发生。您最终只会在不应该执行此操作的线程上销毁该对象,因此可能会被阻塞的时间超过理想值。对于调试,您可以在析构函数中assert表示您不在主线程上。

我要求该类型具有非投掷析构函数并移动构造运算符。

destruction_service<T>维护要被破坏的std::vector<T>个对象。在该向量上提交要销毁的对象push_back()。工作线程等待队列变为非空,然后swap()使用自己的空std::vector<T>。离开临界区后,它clear()向量,从而摧毁所有对象。向量本身保持不变,因此下次可以swap()减少对动态内存分配的需求。如果您担心std::vector永不畏缩,请考虑使用std::deque。我没有使用std::list,因为它为每个项目分配内存,分配内存来销毁对象有些悖论。使用std::list作为工作队列的通常好处是,您不必在关键部分分配内存,但是破坏对象可能是一个低优先级的任务,我不在乎是否只要主线程保持响应,工作线程就会被阻塞一段时间。没有标准方法可以在C ++中设置线程的优先级,但是如果您愿意,可以尝试通过std::thread native_handle(在...中destruction_service给予工作线程低优先级。 destruction_service)的构造函数,给定您的平台允许这样做。

join()的析构函数将#include <cassert> // assert #include <condition_variable> // std::condition_variable #include <mutex> // std::mutex, std::lock_guard, std::unique_lock #include <thread> // std::thread #include <type_traits> // std::is_nothrow_{move_constructible,destructible} #include <utility> // std::move #include <vector> // std::vector template <typename T> class destruction_service final { static_assert(std::is_nothrow_move_constructible<T>::value, "The to-be-destructed object needs a non-throwing move" " constructor or it cannot be safely delivered to the" " destruction thread"); static_assert(std::is_nothrow_destructible<T>::value, "I'm a destruction service, not an ammunition disposal" " facility"); public: using object_type = T; private: // Worker thread destroying objects. std::thread worker_ {}; // Mutex to protect the object queue. mutable std::mutex mutex_ {}; // Condition variable to signal changes to the object queue. mutable std::condition_variable condvar_ {}; // Object queue of to-be-destructed items. std::vector<object_type> queue_ {}; // Indicator that no more objects will be scheduled for destruction. bool done_ {}; public: destruction_service() { this->worker_ = std::thread {&destruction_service::do_work_, this}; } ~destruction_service() noexcept { { const std::lock_guard<std::mutex> guard {this->mutex_}; this->done_ = true; } this->condvar_.notify_all(); if (this->worker_.joinable()) this->worker_.join(); assert(this->queue_.empty()); } void schedule_destruction(object_type&& object) { { const std::lock_guard<std::mutex> guard {this->mutex_}; this->queue_.push_back(std::move(object)); } this->condvar_.notify_all(); } private: void do_work_() { auto things = std::vector<object_type> {}; while (true) { { auto lck = std::unique_lock<std::mutex> {this->mutex_}; if (this->done_) break; this->condvar_.wait(lck, [this](){ return !queue_.empty() || done_; }); this->queue_.swap(things); } things.clear(); } // By now, we may safely modify `queue_` without holding a lock. this->queue_.clear(); } }; 工作线程。如上所述,该类不可复制且不可移动。如果你需要移动它,请将它放在智能指针中。

#include <atomic>   // std::atomic_int
#include <thread>   // std::this_thread::{get_id,yield}
#include <utility>  // std::exchange

#include "destruction_service.hxx"


namespace /* anonymous */
{

  std::atomic_int example_count {};
  std::thread::id main_thread_id {};

  class example
  {

  private:

    int id_ {-1};

  public:

    example() : id_ {example_count.fetch_add(1)}
    {
      std::this_thread::yield();
    }

    example(const example& other) : id_ {other.id_}
    {
    }

    example(example&& other) noexcept : id_ {std::exchange(other.id_, -1)}
    {
    }

    ~example() noexcept
    {
      assert(this->id_ < 0 || std::this_thread::get_id() != main_thread_id);
      std::this_thread::yield();
    }

  };

}  // namespace /* anonymous */


int
main()
{
  main_thread_id = std::this_thread::get_id();
  destruction_service<example> destructor {};
  for (int i = 0; i < 12; ++i)
    {
      auto thing = example {};
      destructor.schedule_destruction(std::move(thing));
    }
}

这是一个简单的用例:

mm-yyyy

感谢Barry reviewing this code并为改进它提出了一些很好的建议。请参阅my question on Code Review以获取较少精简版的代码,但未加入他的建议。