这是个好主意,关闭在类的析构函数类的线程成员?(Is it a good idea to shut

2019-07-30 03:02发布

什么是关闭由一个C ++类时,它的时间为这个类的一个对象管理的升压线程被破坏的最好方法? 我有一个创建并启动建设一个线程,并提供了一个公共类Wake()它唤醒线程当它的时间做一些工作方法。 所述Wake()方法使用升压互斥和升压条件变量的信号线; 线程程序等待条件变量,然后做的工作并返回到等待。

目前,我在类的析构函数关闭这个线程下,使用布尔成员变量的“运行”标志; 我清除标记,然后调用notify_one()条件变量。 线程程序,然后醒来,注意到“跑”是假的,并返回。 下面的代码:

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
    }
}

这是个好主意,关闭在类的析构函数像这样的线程,或者我应该提供一个公共方法,它可以让用户做到这一点的对象被销毁之前,当有错误处理和/或强行破坏螺纹,更有潜力如果线程程序未能返回干净或好时机?

在它的析构清理我对象的混乱是有吸引力的,因为它需要对细节不太注意从用户(抽象,万岁!),但在我看来,我只应该做的事情在析构函数,如果我能保证承担全部责任清理的东西了成功,彻底,并有一个小的机会在类外的代码也许有一天需要知道线程是否被完全关闭。

还有,就是我使用的机制 - 一个线程的堆栈上写的成员变量中的对象和读取该变量在另一个线程 - 安全和稳健?

Answer 1:

它是释放资源类创建类时被破坏,即使资源之一是一个线程是一个好主意。 如果通过用户的呼叫,如明确创建的资源Worker::Start()那么也应该是释放一个明确的方式,如Worker::Stop() 这也将是要么在析构函数中的事件进行清理,用户不会调用一个好主意Worker::Stop()和/或向用户提供一个实现了一个范围的辅助类RAII -idiom,调用Worker::Start()在其构造和Worker::Stop()在其析构函数。 但是,如果资源分配是隐式进行,如在Worker的构造函数,那么资源的释放也应该是隐式的,离开析构函数作为总理候选人这一责任。


毁坏

让我们看Worker::~Worker() 一般的规则是在析构函数不能抛出异常 。 如果Worker对象是堆栈从另一个例外退绕,并且Worker::~Worker()抛出异常,则std::terminate()将被调用,杀死该应用程序。 虽然Worker::~Worker()未明确抛出一个异常,但考虑到一些正在调用可能抛出的功能是非常重要的:

  • m_Condition.notify_one()不会抛出。
  • m_pThread->join()可能抛出boost::thread_interrupted

如果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 */ 
  }
}

并发

管理线程可能会非常棘手。 定义的功能,如确切的期望的行为是非常重要的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::ThreadProc只检查m_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()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
  }
}

注:由于个人喜好,我选择不分配boost::thread在堆上,并作为一个结果,我并不需要通过管理它boost::scoped_ptrboost::thread具有默认的构造函数 ,以引用不-一个线程,它是移动转让的 。



文章来源: Is it a good idea to shut down a class's thread member in the class's destructor?