为什么这个线程管理模式会导致死锁? [英] Why might this thread management pattern result in a deadlock?

查看:131
本文介绍了为什么这个线程管理模式会导致死锁?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用一个公共基类 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


  1. createThread )获取 threads_lock

  2. boost :: thread :: opeator = )获取 boost :: thread 内部锁

  3. boost :: thread :: opeator = 内部锁

  4. createThread 线程结束处理程序: p>


    1. at_thread_exit )获取 boost :: thread internal lock

    2. releaseThread )获取 threads_lock / li>
    3. releaseThread )release threads_lock

    4. 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 a boost::thread.

    Instances of has_threads each own a set of threads (to support waitAll and interruptAll functions, which I do not include below), and should automatically invoke removeThread when a thread terminates to maintain this set'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 output ABC.

    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 than A. [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 protect threads.

    Update:

    To expand on something in the comments with speculation about how boost::thread works, the lock patterns could look something like this:

    createThread:

    1. (createThread) obtain threads_lock
    2. (boost::thread::opeator =) obtain a boost::thread internal lock
    3. (boost::thread::opeator =) release a boost::thread internal lock
    4. (createThread) release threads_lock

    thread end handler:

    1. (at_thread_exit) obtain a boost::thread internal lock
    2. (releaseThread) obtain threads_lock
    3. (releaseThread) release threads_lock
    4. (at_thread_exit) release a boost: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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆