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()没有明确地抛出异常,但重要的是要考虑它调用的一些函数可能抛出:
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 */
}
}
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::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()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,它是可移动赋值的.