为什么这个线程管理模式会导致死锁?

时间:2011-12-08 17:05:32

标签: c++ deadlock boost-thread

我正在使用公共基类has_threads来管理应该允许实例化boost::thread的任何类型。

has_threads的实例各拥有set thread个{支持waitAllinterruptAll个功能,我在下面未包含这些功能,以及应该在线程终止时自动调用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
    }
};

3 个答案:

答案 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

  1. createThread)获取threads_lock
  2. boost::thread::opeator =)获取boost::thread内部锁定
  3. boost::thread::opeator =)发布boost::thread内部锁定
  4. createThread)发布threads_lock
  5. 线程结束处理程序:

    1. at_thread_exit)获取boost::thread内部锁定
    2. releaseThread)获取threads_lock
    3. releaseThread)发布threads_lock
    4. at_thread_exit)发布boost:thread内部锁定
    5. 如果这两个boost::thread锁是同一个锁,则可能会出现死锁的可能性。但这是推测,因为大部分提升代码让我害怕,我尽量不去看它。

      可以/应该重新设计{p> 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);
    }