我正在使用公共基类has_threads
来管理应该允许实例化boost::thread
的任何类型。
has_threads
的实例各拥有set
thread
个{支持waitAll
和interruptAll
个功能,我在下面未包含这些功能,以及应该在线程终止时自动调用removeThread
以保持set
的完整性。
在我的计划中,我只有其中一个。线程每10秒创建一次,每次执行数据库查找。查找完成后,线程将运行完成,并应调用removeThread
;使用互斥锁设置,线程对象将从内部跟踪中删除。我可以通过输出ABC
看到它正常工作。
但有时,机制会发生冲突。 removeThread
可能同时执行两次。我无法弄清楚为什么会导致死锁。从这一点开始的所有线程调用都不会输出A
以外的任何内容。 [值得注意的是我正在使用线程安全的stdlib,并且在没有使用IOStream时问题仍然存在。] 堆栈跟踪表明互斥锁正在锁定这些线程,但为什么锁不会最终由第一个线程发布第二个,然后第二个发布第三个,依此类推?
我是否遗漏了scoped_lock
如何运作的基本信息?虽然(或者甚至由于?)使用互斥锁,但我在这里有什么明显的错过可能会导致死锁吗?
对不起这个糟糕的问题感到抱歉,但是我确信你知道它几乎不可能为这样的bug提供真正的测试用例。
class has_threads {
protected:
template <typename Callable>
void createThread(Callable f, bool allowSignals)
{
boost::mutex::scoped_lock l(threads_lock);
// Create and run thread
boost::shared_ptr<boost::thread> t(new boost::thread());
// Track thread
threads.insert(t);
// Run thread (do this after inserting the thread for tracking so that we're ready for the on-exit handler)
*t = boost::thread(&has_threads::runThread<Callable>, this, f, allowSignals);
}
private:
/**
* Entrypoint function for a thread.
* Sets up the on-end handler then invokes the user-provided worker function.
*/
template <typename Callable>
void runThread(Callable f, bool allowSignals)
{
boost::this_thread::at_thread_exit(
boost::bind(
&has_threads::releaseThread,
this,
boost::this_thread::get_id()
)
);
if (!allowSignals)
blockSignalsInThisThread();
try {
f();
}
catch (boost::thread_interrupted& e) {
// Yes, we should catch this exception!
// Letting it bubble over is _potentially_ dangerous:
// http://stackoverflow.com/questions/6375121
std::cout << "Thread " << boost::this_thread::get_id() << " interrupted (and ended)." << std::endl;
}
catch (std::exception& e) {
std::cout << "Exception caught from thread " << boost::this_thread::get_id() << ": " << e.what() << std::endl;
}
catch (...) {
std::cout << "Unknown exception caught from thread " << boost::this_thread::get_id() << std::endl;
}
}
void has_threads::releaseThread(boost::thread::id thread_id)
{
std::cout << "A";
boost::mutex::scoped_lock l(threads_lock);
std::cout << "B";
for (threads_t::iterator it = threads.begin(), end = threads.end(); it != end; ++it) {
if ((*it)->get_id() != thread_id)
continue;
threads.erase(it);
break;
}
std::cout << "C";
}
void blockSignalsInThisThread()
{
sigset_t signal_set;
sigemptyset(&signal_set);
sigaddset(&signal_set, SIGINT);
sigaddset(&signal_set, SIGTERM);
sigaddset(&signal_set, SIGHUP);
sigaddset(&signal_set, SIGPIPE); // http://www.unixguide.net/network/socketfaq/2.19.shtml
pthread_sigmask(SIG_BLOCK, &signal_set, NULL);
}
typedef std::set<boost::shared_ptr<boost::thread> > threads_t;
threads_t threads;
boost::mutex threads_lock;
};
struct some_component : has_threads {
some_component() {
// set a scheduler to invoke createThread(bind(&some_work, this)) every 10s
}
void some_work() {
// usually pretty quick, but I guess sometimes it could take >= 10s
}
};
答案 0 :(得分:2)
好吧,如果同一个线程锁定它已经锁定的互斥锁(除非你使用递归互斥锁),可能会发生死锁。
如果第二次使用与代码相同的线程调用发布部分,则会出现死锁。
我没有详细研究过您的代码,但您可能需要重新设计代码(简化?)以确保同一个线程无法获取锁定两次。您可以使用安全措施检查锁的所有权......
编辑: 正如我在评论和IronMensan的回答中所说,一个可能的情况是线程在创建期间停止,at_exit在锁定在代码创建部分中的互斥锁发布之前被调用。
EDIT2:
好吧,使用互斥锁和范围锁,我只能想象一个递归锁或一个未释放的锁。例如,如果由于内存损坏而导致循环变为无限,则可能发生这种情况。
我建议添加更多带有线程ID的日志来检查是否有递归锁或奇怪的东西。然后我会检查我的循环是否正确。我还将检查每个线程只调用一次at_exit ...
还有一件事,在at_exit函数中检查线程的擦除(因此调用析构函数)的效果......
我的2美分答案 1 :(得分:2)
您可能需要执行以下操作:
void createThread(Callable f, bool allowSignals)
{
// Create and run thread
boost::shared_ptr<boost::thread> t(new boost::thread());
{
boost::mutex::scoped_lock l(threads_lock);
// Track thread
threads.insert(t);
}
//Do not hold threads_lock while starting the new thread in case
//it completes immediately
// Run thread (do this after inserting the thread for tracking so that we're ready for the on-exit handler)
*t = boost::thread(&has_threads::runThread<Callable>, this, f, allowSignals);
}
换句话说,仅使用thread_lock
来保护threads
。
<强>更新强>
为了扩展评论中有关boost :: thread如何工作的内容,锁模式看起来像这样:
createThread
:
createThread
)获取threads_lock
boost::thread::opeator =
)获取boost::thread
内部锁定boost::thread::opeator =
)发布boost::thread
内部锁定createThread
)发布threads_lock
线程结束处理程序:
at_thread_exit
)获取boost::thread
内部锁定releaseThread
)获取threads_lock
releaseThread
)发布threads_lock
at_thread_exit
)发布boost:thread
内部锁定如果这两个boost::thread
锁是同一个锁,则可能会出现死锁的可能性。但这是推测,因为大部分提升代码让我害怕,我尽量不去看它。
createThread
以在第一步和第二步之间移动第4步并消除潜在的死锁。
答案 2 :(得分:1)
在createThread
中的赋值运算符完成之前或期间,创建的线程可能正在完成。使用事件队列或可能需要的其他结构。虽然一个更简单的,但是黑客攻击的解决方案也可能有效。不要更改createThread
,因为您必须使用threads_lock
来保护threads
本身及其指向的thread
个对象。而是将runThread更改为:
template <typename Callable>
void runThread(Callable f, bool allowSignals)
{
//SNIP setup
try {
f();
}
//SNIP catch blocks
//ensure that createThread is complete before this thread terminates
boost::mutex::scoped_lock l(threads_lock);
}