为什么这个线程管理模式会导致死锁? [英] Why might this thread management pattern result in a deadlock?
问题描述
我使用一个公共基类 has_threads
来管理应该允许实例化一个 boost :: thread
。
的实例has_threads
各自拥有集
线程
(支持 waitAll
和 interruptAll
当线程终止以维护 set
'时,应该自动调用 removeThread
在我的程序中,我只有其中一个。线程每隔10秒创建一个间隔,并且每个线程都执行一次数据库查找。当查找完成时,线程运行到完成,并且应该调用 removeThread
;使用互斥集,线程对象从内部跟踪中删除。我可以看到这个工作正常与输出 ABC
。
一段时间后,机制碰撞。 removeThread
可能同时执行两次。我不能弄清楚的是为什么这会导致死锁。从这一点开始的所有线程调用不会输出 A
以外的任何东西。 [值得注意的是,我使用线程安全的stdlib,并且问题仍然是IOStreams不使用时。] 堆栈跟踪表明互斥锁定这些线程,但为什么锁不会
我缺少关于如何的一些基本知识, scoped_lock
工程?有没有什么明显的在这里,我错过了,可能会导致死锁,尽管(或甚至由于?)使用互斥锁吗?
不好的问题,但因为我敢肯定你知道这是几乎不可能提出真正的测试盒这样的错误。
class has_threads {
protected:
template< typename Callable>
void createThread(Callable f,bool allowSignals)
{
boost :: mutex :: scoped_lock l(threads_lock);
//创建和运行线程
boost :: shared_ptr< boost :: thread> t(new boost :: thread());
//跟踪线程
threads.insert(t);
//运行线程(在插入线程以进行跟踪之后执行此操作,以便我们为退出处理程序做好准备)
* t = boost :: thread(& has_threads: :runThread< Callable>,this,f,allowSignals);
}
private:
/ **
*线程的入点函数。
*设置端到端处理程序,然后调用用户提供的工作程序函数。
* /
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){
//是的,我们应该捕获这个异常!
//让它冒泡是_potentially_危险:
// http://stackoverflow.com/questions/6375121
std :: cout< Thread<< boost :: this_thread :: get_id()<< 中断(和结束)。 << std :: endl;
}
catch(std :: exception& e){
std :: cout< 从线程捕获的异常< boost :: this_thread :: get_id()<< :<< e.what()< std :: endl;
}
catch(...){
std :: cout< 未知异常从线程捕获< boost :: this_thread :: get_id()<< std :: endl;
}
}
void has_threads :: releaseThread(boost :: thread :: id thread_id)
{
std :: cout< 一个;
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(){
//每10s设置一个调度程序来调用createThread(bind(& some_work,this))
}
void some_work(){
//通常很快,但我想有时它可以采用> = 10s
}
};
您可能需要这样做: p>
void createThread(Callable f,bool allowSignals)
{
//创建并运行线程
boost :: shared_ptr< boost :: thread> t(new boost :: thread());
{
boost :: mutex :: scoped_lock l(threads_lock);
//跟踪线程
threads.insert(t);
}
//在启动新线程时不保持threads_lock,如果
//立即完成
//运行线程插入线程用于跟踪,以便我们准备好在退出处理程序)
* t = boost :: thread(& has_threads :: runThread< Callable> this,f,allowSignals);
}
换句话说,使用 thread_lock
专用于保护线程
。
更新: b
$ b
为了在注释中展开一些关于boost :: thread如何工作的猜测,锁模式看起来像这样:
createThread
:
- (
createThread
)获取threads_lock
- (
boost :: thread :: opeator =
)获取boost :: thread
内部锁 - (
boost :: thread :: opeator = 内部锁
- (
createThread 线程结束处理程序: p>
- (
at_thread_exit
)获取boost :: thread
internal lock
- (
releaseThread
)获取threads_lock
/ li>
- (
releaseThread
)releasethreads_lock
- (
at_thread_exit
)发布boost:thread
内部锁
如果这两个
boost :: thread
锁是同一个锁,死锁的可能性是清楚的。但这是推测,因为大部分boost代码吓唬我,我尽量不要看它。
createThread
可以/应该重做,以在步骤1和步骤2之间移动步骤4,消除潜在的死锁。I'm using a common base class
has_threads
to manage any type that should be allowed to instantiate aboost::thread
.Instances of
has_threads
each own aset
ofthread
s (to supportwaitAll
andinterruptAll
functions, which I do not include below), and should automatically invokeremoveThread
when a thread terminates to maintain thisset
's integrity.In my program, I have just one of these. Threads are created on an interval every 10s, and each performs a database lookup. When the lookup is complete, the thread runs to completion and
removeThread
should be invoked; with a mutex set, the thread object is removed from internal tracking. I can see this working properly with the outputABC
.Once in a while, though, the mechanisms collide.
removeThread
is executed perhaps twice concurrently. What I can't figure out is why this results in a deadlock. All thread invocations from this point never output anything other thanA
. [It's worth noting that I'm using thread-safe stdlib, and that the issue remains when IOStreams are not used.] Stack traces indicate that the mutex is locking these threads, but why would the lock not be eventually released by the first thread for the second, then the second for the third, and so on?Am I missing something fundamental about how
scoped_lock
works? Is there anything obvious here that I've missed that could lead to a deadlock, despite (or even due to?) the use of a mutex lock?Sorry for the poor question, but as I'm sure you're aware it's nigh-on impossible to present real testcases for bugs like this.
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 } };
解决方案You may need to do something like this:
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); }
In other words, use
thread_lock
exclusively to protectthreads
.Update:
To expand on something in the comments with speculation about how boost::thread works, the lock patterns could look something like this:
createThread
:- (
createThread
) obtainthreads_lock
- (
boost::thread::opeator =
) obtain aboost::thread
internal lock - (
boost::thread::opeator =
) release aboost::thread
internal lock - (
createThread
) releasethreads_lock
thread end handler:
- (
at_thread_exit
) obtain aboost::thread
internal lock - (
releaseThread
) obtainthreads_lock
- (
releaseThread
) releasethreads_lock
- (
at_thread_exit
) release aboost:thread
internal lock
If those two
boost::thread
locks are the same lock, the potential for deadlock is clear. But this is speculation because much of the boost code scares me and I try not to look at it.createThread
could/should be reworked to move step 4 up between steps one and two and eliminate the potential deadlock.这篇关于为什么这个线程管理模式会导致死锁?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
- (