【问题标题】:Deadlock with no user code没有用户代码的死锁
【发布时间】:2015-02-06 20:08:42
【问题描述】:

我的 C++ 程序出现死锁,该程序使用了 std::thread、std::mutex、std::condition_variable 等。

这本身并不奇怪,直到我查看进程中每个线程的堆栈:

8532    0   Main Thread Main Thread msvcr120.dll!Concurrency::details::ExternalContextBase::Block   Normal
                        ntdll.dll!_ZwWaitForSingleObject@12()    
                        KernelBase.dll!_WaitForSingleObjectEx@12()   
                        kernel32.dll!_WaitForSingleObjectExImplementation@12()   
                        msvcr120.dll!Concurrency::details::ExternalContextBase::Block() Line 145     
                        ntdll.dll!_ZwQueryVirtualMemory@24()     
                        kernel32.dll!_BasepFillUEFInfo@8()   
                        ntdll.dll!_ZwQueryInformationProcess@20()    
                        msvcr120.dll!_initterm(void (void) * * pfbegin, void (void) * * pfend) Line 954  

-

6484    0   Worker Thread   ntdll.dll!_TppWaiterpThread@4() ntdll.dll!_NtWaitForMultipleObjects@20  Normal
                        ntdll.dll!_NtWaitForMultipleObjects@20()     
                        ntdll.dll!_TppWaiterpThread@4()  
                        kernel32.dll!@BaseThreadInitThunk@12()   
                        ntdll.dll!___RtlUserThreadStart@8()  
                        ntdll.dll!__RtlUserThreadStart@8()   

-

6296    0   Worker Thread   msvcr120.dll!_threadstartex msvcr120.dll!Concurrency::details::ExternalContextBase::Block   Normal
                        ntdll.dll!_ZwWaitForSingleObject@12()    
                        KernelBase.dll!_WaitForSingleObjectEx@12()   
                        kernel32.dll!_WaitForSingleObjectExImplementation@12()   
                        msvcr120.dll!Concurrency::details::ExternalContextBase::Block() Line 145     
                        msvcp120.dll!std::_Thrd_startX(struct _Thrd_imp_t *,unsigned int (*)(void *),void *)     
                        msvcr120.dll!_callthreadstartex() Line 376   
                        msvcr120.dll!_threadstartex(void * ptd) Line 354     
                        kernel32.dll!@BaseThreadInitThunk@12()   
                        ntdll.dll!___RtlUserThreadStart@8()  
                        ntdll.dll!__RtlUserThreadStart@8()   

似乎没有线程在执行我的代码,而且我知道我们已经进入 main,因为程序在挂起之前已经做了一些事情。

我正在使用以下类与我的 std::thread 进行通信,以防我在那里犯了一些错误:

template <typename T>
class BlockingQueue
{
public:
    BlockingQueue() : _active(true) {}

    bool Get(T& out)
    {
        std::unique_lock<std::mutex> lock(_mutex);

        _cv.wait(lock, [&](){ return !_queue.empty() || !_active; });

        if (_queue.empty())
        {
            assert(!_active);

            return false;
        }

        out = std::move(_queue.front());
        _queue.pop();

        return true;
    }

    void Put(const T& in)
    {
        {
            std::unique_lock<std::mutex> lock(_mutex);

            _queue.push(in);
        }

        _cv.notify_one();
    }

    void Put(T&& in)
    {
        {
            std::unique_lock<std::mutex> lock(_mutex);

            _queue.push(std::move(in));
        }

        _cv.notify_one();
    }

    void Finish()
    {
        {
            std::unique_lock<std::mutex> lock(_mutex);

            _active = false;
        }

        _cv.notify_all();
    }

private:
    bool _active;
    std::mutex _mutex;
    std::condition_variable _cv;
    std::queue<T> _queue;
};

我现在有两个想法:

  1. Main 由于某种原因已经退出。这是一个 PoC,所以当出现错误时,我们会记录到 stdout 并调用 exit()(是的,我知道,这不是最好的,这是从另一个用 C++ 编写的 C 风格程序改编而来的)。我没有看到任何内容被记录到终端,但我想可能是输出正在缓冲并且尚未写出?
  2. 调试器在某种程度上对我撒谎。通常它会在执行此操作时将[frames below may be missing/incorrect] 放入堆栈跟踪中,但如果不这样做,它可能会发生。

【问题讨论】:

  • 您应该通知内部互斥锁,否则您可能会遇到竞争: 1. _cv.wait() 评估谓词;它返回false 2. notify_all 被调用 3. wait(lock)_cv.wait() 中被调用但不确定这是否是问题。
  • 我在cppreference.com上看std::condition_variable的文档,好像说通知的时候不需要持有锁,其实这样做是一种表现悲观化(这是有道理的,因为由于通知而唤醒的任何线程都会立即尝试获取锁并失败)
  • "通知线程不需要持有与等待线程持有的同一个互斥锁;实际上这样做是一种悲观,因为被通知的线程会立即阻塞再次,等待通知线程释放锁。然而,一些实现(特别是 pthread 的许多实现)认识到这种情况并通过将等待线程从条件变量的队列直接转移到通知调用中互斥体的队列,而不唤醒它。“就是他们所说的。
  • 在你的函数中放一些调试日志,看看它在哪里结束
  • 嗯,这取决于情况。但是我检查了 C++11 中的 condition_variable::wait,它说在每次调用谓词之前都需要重新获取锁,所以它确实不能为 _active 竞争。

标签: c++ multithreading c++11 visual-studio-2013 deadlock


【解决方案1】:

原来我未能替换队列中的项目,导致我的线程在从队列中检索时死锁,这意味着调试器在欺骗我。 :(

【讨论】:

    猜你喜欢
    • 2021-06-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-02
    • 1970-01-01
    相关资源
    最近更新 更多