在类的析构函数中关闭类的线程成员是一个好主意吗?

byt*_*uff 18 c++ multithreading boost destructor

当该类的对象被销毁时,关闭由C++类管理的Boost线程的最佳方法是什么?我有一个类,它在构造时创建并启动一个线程,并提供一个公共Wake()方法,当它需要做一些工作时唤醒线程.该Wake()方法使用Boost互斥锁和Boost条件变量来表示线程; 线程程序等待条件变量,然后完成工作并返回等待.

目前,我在类的析构函数中关闭了这个线程,使用布尔成员变量作为"运行"标志; 我清除该标志,然后在条件变量上调用notify_one().然后线程程序唤醒,注意"running"为false,并返回.这是代码:

class Worker
{
public:
    Worker();
    ~Worker();
    void Wake();
private:
    Worker(Worker const& rhs);             // prevent copying
    Worker& operator=(Worker const& rhs);  // prevent assignment
    void ThreadProc();
    bool m_Running;
    boost::mutex               m_Mutex;
    boost::condition_variable  m_Condition;
    boost::scoped_ptr<boost::thread> m_pThread;
};

Worker::Worker()
    : m_Running(true)
    , m_Mutex()
    , m_Condition()
    , m_pThread()
{
    m_pThread.reset(new boost::thread(boost::bind(&Worker::ThreadProc, this)));
}

Worker::~Worker()
{
    m_Running = false;
    m_Condition.notify_one();
    m_pThread->join();
}

void Worker::Wake()
{
    boost::lock_guard<boost::mutex> lock(m_Mutex);
    m_Condition.notify_one();
}

void Worker::ThreadProc()
{
    for (;;)
    {
        boost::unique_lock<boost::mutex> lock(m_Mutex);
        m_Condition.wait(lock);
        if (! m_Running) break;
        // do some work here
    }
}
Run Code Online (Sandbox Code Playgroud)

像这样关闭类的析构函数中的线程是一个好主意,还是应该提供一个公共方法,让用户在对象被销毁之前执行此操作,此时更有可能进行错误处理和/或强行销毁线程如果线程程序未能干净或及时返回?

清理我的对象在其析构函数中的混乱是有吸引力的,因为它将需要较少关注用户的细节(抽象,欢呼!)但在我看来,如果我可以保证完全负责,我应该只在析构函数中做事情成功彻底地清理事物,并且有一天类外的代码可能需要知道线程是否干净地关闭.

另外,我正在使用的机制 - 写入一个线程堆栈中的对象中的成员变量并在另一个线程中读取该变量 - 安全且理智吗?

Tan*_*ury 47

当类被销毁时,释放类创建的资源是个好主意,即使其中一个资源是一个线程.如果资源是通过用户调用显式创建的,例如Worker::Start(),那么还应该有一种明确的方式来释放它,例如Worker::Stop().如果用户没有调用Worker::Stop()和/或为用户提供实现RAII -idiom 的作用域助手类,Worker::Start()在其构造函数和析构函数中调用,那么在析构函数中执行清理也是一个好主意Worker::Stop().但是,如果资源分配是隐式进行,如在Worker构造函数,那么资源的释放也应该是隐式的,离开析构函数作为总理候选人这一责任.


毁坏

让我们来看看Worker::~Worker().一般规则是不在析构函数中抛出异常.如果Worker对象位于从另一个异常展开的堆栈上,并Worker::~Worker()抛出异常,std::terminate()则将调用该对象,从而终止该应用程序.虽然Worker::~Worker()没有明确地抛出异常,但重要的是要考虑它调用的一些函数可能抛出:

如果std::terminate()是所需的行为,则不需要进行任何更改.但是,如果std::terminate()不需要,则捕获boost::thread_interrupted并抑制它.

Worker::~Worker()
{
  m_Running = false;
  m_Condition.notify_one();
  try
  {
    m_pThread->join();
  }
  catch ( const boost::thread_interrupted& )
  {
    /* suppressed */ 
  }
}
Run Code Online (Sandbox Code Playgroud)

并发

管理线程可能很棘手.重要的是定义函数的确切期望行为Worker::Wake(),以及理解促进线程和同步的类型的行为.例如,boost::condition_variable::notify_one()如果没有阻塞线程,则无效boost::condition_variable::wait().让我们检查可能的并发路径Worker::Wake().

下面是两个场景的并发图的粗略尝试:

  • 操作顺序从上到下发生.(即顶部的操作发生在底部操作之前.
  • 并发操作写在同一行.
  • <>用于突出显示一个线程唤醒或解除阻塞另一个线程的时间.例如,A > B表示该线程A正在解除阻塞线程B.

场景:Worker::Wake()Worker::ThreadProc()被阻止时调用m_Condition.

Other Thread                       | Worker::ThreadProc
-----------------------------------+------------------------------------------
                                   | lock( m_Mutex )
                                   | `-- m_Mutex.lock()
                                   | m_Condition::wait( lock )
                                   | |-- m_Mutex.unlock()
                                   | |-- waits on notification
Worker::Wake()                     | |
|-- lock( m_Mutex )                | |
|   `-- m_Mutex.lock()             | |
|-- m_Condition::notify_one()      > |-- wakes up from notification
`-- ~lock()                        | `-- m_Mutex.lock() // blocks
    `-- m_Mutex.unlock()           >     `-- // acquires lock
                                   | // do some work here
                                   | ~lock() // end of for loop's scope
                                   | `-- m_Mutex.unlock()

结果:Worker::Wake()返回相当快,并Worker::ThreadProc运行.


场景:未被阻止时Worker::Wake()调用.Worker::ThreadProc()m_Condition

Other Thread                       | Worker::ThreadProc
-----------------------------------+------------------------------------------
                                   | lock( m_Mutex )
                                   | `-- m_Mutex.lock()
                                   | m_Condition::wait( lock )
                                   | |-- m_Mutex.unlock()
Worker::Wake()                     > |-- wakes up
                                   | `-- m_Mutex.lock()
Worker::Wake()                     | // do some work here
|-- lock( m_Mutex )                | // still doing work...
|   |-- m_Mutex.lock() // block    | // hope we do not block on a system call
|   |                              | // and more work...
|   |                              | ~lock() // end of for loop's scope
|   |-- // still blocked           < `-- m_Mutex.unlock()
|   `-- // acquires lock           | lock( m_Mutex ) // next 'for' iteration.
|-- m_Condition::notify_one()      | `-- m_Mutex.lock() // blocked
`-- ~lock()                        |     |-- // still blocked
    `-- m_Mutex.unlock()           >     `-- // acquires lock
                                   | m_Condition::wait( lock )    
                                   | |-- m_Mutex.unlock()
                                   | `-- waits on notification
                                   |     `-- still waiting...

结果:工作Worker::Wake()被阻止Worker::ThreadProc,但是没有操作,因为它m_Condition在没有人等待时发送通知.

这不是特别危险Worker::Wake(),但它可能会导致问题Worker::~Worker().如果Worker::~Worker()Worker::ThreadProc执行工作时运行,则Worker::~Worker()可能在加入线程时无限期地阻塞,因为线程可能不会m_Condition在通知它的位置等待,并且Worker::ThreadProcm_Running在完成等待之后进行检查m_Condition.


努力寻求解决方案

在此示例中,我们定义以下要求:

  • Worker::~Worker()不会导致std::terminate()被调用.
  • Worker::Wake()Worker::ThreadProc做工作时不会阻止.
  • 如果Worker::Wake()Worker::ThreadProc没有工作的情况下被调用,那么它将通知Worker::ThreadProc工作.
  • 如果在工作时Worker::Wake()调用Worker::ThreadProc,那么它将通知Worker::ThreadProc执行另一次迭代的工作.
  • 多次调用Worker::Wake()while Worker::ThreadProc工作将导致Worker::ThreadProc执行一次额外的迭代工作.

码:

#include <boost/thread.hpp>

class Worker
{
public:
  Worker();
  ~Worker();
  void Wake();
private:
  Worker(Worker const& rhs);             // prevent copying
  Worker& operator=(Worker const& rhs);  // prevent assignment
  void ThreadProc();

  enum state { HAS_WORK, NO_WORK, SHUTDOWN };

  state                            m_State;
  boost::mutex                     m_Mutex;
  boost::condition_variable        m_Condition;
  boost::thread                    m_Thread;
};

Worker::Worker()
  : m_State(NO_WORK)
  , m_Mutex()
  , m_Condition()
  , m_Thread()
{
  m_Thread = boost::thread(&Worker::ThreadProc, this);
}

Worker::~Worker()
{
  // Create scope so that the mutex is only locked when changing state and
  // notifying the condition.  It would result in a deadlock if the lock was
  // still held by this function when trying to join the thread.
  {
    boost::lock_guard<boost::mutex> lock(m_Mutex);
    m_State = SHUTDOWN;
    m_Condition.notify_one();
  }
  try { m_Thread.join(); }
  catch ( const boost::thread_interrupted& ) { /* suppress */ };
}

void Worker::Wake()
{
  boost::lock_guard<boost::mutex> lock(m_Mutex);
  m_State = HAS_WORK;
  m_Condition.notify_one();
}

void Worker::ThreadProc()
{
  for (;;)
  {
    // Create scope to only lock the mutex when checking for the state.  Do
    // not continue to hold the mutex wile doing busy work.
    {
      boost::unique_lock<boost::mutex> lock(m_Mutex);
      // While there is no work (implies not shutting down), then wait on
      // the condition.
      while (NO_WORK == m_State)
      {
        m_Condition.wait(lock);
        // Will wake up from either Wake() or ~Worker() signaling the condition
        // variable.  At that point, m_State will either be HAS_WORK or
        // SHUTDOWN.
      }
      // On shutdown, break out of the for loop.
      if (SHUTDOWN == m_State) break;
      // Set state to indicate no work is queued.
      m_State = NO_WORK;
    }

    // do some work here
  }
}
Run Code Online (Sandbox Code Playgroud)

注意:作为个人偏好,我选择不在boost::thread堆上分配,因此,我不需要通过它来管理它boost::scoped_ptr. boost::thread有一个默认的构造函数,它将引用Not-a-Thread,它是可移动赋值的.

  • 哇。支持您,也是最喜欢的问题,这是一个答案,我敢肯定,我会回来很多次。 (2认同)