std::thread 在无限循环中随机时间后锁定

Posted

技术标签:

【中文标题】std::thread 在无限循环中随机时间后锁定【英文标题】:std::thread locks after a random time in an endless loop 【发布时间】:2015-08-23 08:52:21 【问题描述】:

我正在尝试为我的主应用程序实现 3 个额外的线程来执行非共享操作。

起初我认为它可以工作,因为如果我取消注释 WorkerThread 函数中的最后一个 printf 调用,它不会在随机时间段后锁定在 WaitThread() 处。如果没有 printf,它有时需要几秒钟才能锁定在 mWaitCond.Wait() 函数上,有时是在启动之后。 printf 似乎修复了线程的时间。

应用程序不会崩溃,只是应用程序的 cpu 使用率变为 0%(以及每个线程)并且它没有响应。在 Visual Studio 调试器中停止将 WaitThread() 函数中的 while(mWakeUp) mWaitCondition.Wait() 行显示为当前位置。它还表明 mWakeUp 对于所有线程都是错误的,所以它不应该停留在那个 while 循环中。

我的设计理念:

    SetupThreads() 在进入主无限循环之前被调用 在无限循环中,WorkerInit() 被调用来唤醒线程 在访问 3 个线程的数据之前,会调用 WorkerWait() 等待它们完成 在 WorkerThread 函数内部(由每个线程调用),我锁定互斥体并等待线程被唤醒或停止 处理完数据后,wakeUp 设置为 false 并且 condition_variable 通知

可能是waitthread一个接一个地等待线程,当它等待让我们说索引0处的线程时,索引2处的线程继续运行?

    static const ui32 NumContexts = 3;

    // array of pointers to threads
    std::thread* mThreadHandles[NumContexts];

    // wakup
    std::atomic<bool> mWakeUp[NumContexts];
    std::mutex mWakeMutex[NumContexts];
    std::condition_variable mWakeCondition[NumContexts];

    // wait for thread to finish task
    std::mutex mWaitMutex[NumContexts];
    std::condition_variable mWaitCondition[NumContexts];

    // stop signal
    std::atomic<bool> mStop[NumContexts];

    void Framework::SetupThreading()
    
        // create and start threads
        for (int i = 0; i < NumContexts; i++)
        
            this->mWakeUp[i] = false;
            this->mStop[i] = false;
            this->mThreadHandles[i] = new  std::thread(&Framework::WorkerThread, this, reinterpret_cast<void*>(i));
        
    

    //---------------------------------------------
    void Framework::WakeUpThread(int i)
    
        
            //auto lock = std::unique_lock<std::mutex>(this->mWakeMutex[i]);
            std::lock_guard<std::mutex> lock(this->mWakeMutex[i]);
            //printf("Waking up thread %i \n", i);

            this->mWakeUp[i] = true;
        
        this->mWakeCondition[i].notify_one();
    

    // THIS FUNCTION LOCKS
    //---------------------------------------------
    void Framework::WaitThread(int i)
    
        auto lock = std::unique_lock<std::mutex>(this->mWaitMutex[i]);
        //printf("Waiting for thread %i to finish \n", i);

        while (this->mWakeUp[i])
            this->mWaitCondition[i].wait(lock);

        //printf("Thread %i finished! \n", i);
    

    //---------------------------------------------
    void Framework::StopThread(int i)
    
        auto lock = std::unique_lock<std::mutex>(this->mWakeMutex[i]);
        printf("Sending stop signal for thread %i \n", i);
        this->mStop[i] = true;
        this->mWakeCondition[i].notify_one();
    

    //---------------------------------------------
    void Framework::JoinThread(int i)
    
        printf("Waiting for join of thread %i \n", i);
        this->mThreadHandles[i]->join();
        printf("Thread %i joined! \n", i);
    

    // THESE ARE CALLED IN THE MAIN LOOP
    //---------------------------------------------
    void Framework::WorkerInit()
    
        for (int i = 0; i < NumContexts; i++)
        
            this->WakeUpThread(i);
        
    

    void Framework::WorkerWait()
    
        for (int i = 0; i < NumContexts; i++)
        
            this->WaitThread(i);
        
    

    // THE FUNCTION CALLED BY THE THREADS
    //---------------------------------------------
    void Framework::WorkerThread(LPVOID workerIndex)
    
        int threadIndex = reinterpret_cast<int>(workerIndex);
        while (threadIndex < NumContexts && threadIndex >= 0)
        
            
                auto lock = std::unique_lock<std::mutex>(this->mWakeMutex[threadIndex]);
                //printf("thread %i: waiting for wakeup or stop signal...\n", threadIndex);

                // not stopped nor woken up? continue to wait
                while (this->mWakeUp[threadIndex] == false && this->mStop[threadIndex] == false)
                
                    this->mWakeCondition[threadIndex].wait(lock);
                

                // stop signal sent?
                if (this->mStop[threadIndex])
                
                    //printf("thread %i: got stop signal!\n", threadIndex);
                    return;
                
                //printf("thread %i: got wakeup signal!\n", threadIndex);

                // lock unlocks here (lock destructor)
            

            //  printf("thread %i: running the task...\n", threadIndex);

             // RUN CODE HERE

                //printf("thread %i finished! Sending signal!...\n", threadIndex);

                // m_wakeup is atomic so there is no concurrency issue with wait()
                this->mWakeUp[threadIndex] = false;
                this->mWaitCondition[threadIndex].notify_all();


        
    

【问题讨论】:

这可能不是您潜在死锁问题的解决方案,但您的代码似乎有几个经典的数据竞争。您可以使用互斥锁来避免这些。要弄清楚它们在哪里:询问每个变量是否可以从多个线程访问,其中至少有一个线程修改变量。如果没有同步原语,比如互斥锁,那就是数据竞争。还要考虑一下,对于您的问题,可能有一种更简单的方法。 我使用 D3D12s CommandLists 来记录命令。每个线程都有自己的数据数组和命令列表,因此不能共享访问任何数据。如果我使用 printf 显示线程的状态,它也可以正常工作:codepaste.net/wmqgao 如果我不使用线程处理任何内容,它也会锁定 例如this-&gt;mWakeUp[i] = false; 你修改了没有同步原语的状态。请注意,原子性本身并不是您需要的那种并发性。但是,在强排序的 CPU 上,这个问题不会变得明显。这仍然是一场数据竞赛。 @CouchDeveloper 不,对原子变量的访问不能 - 根据定义 - 参与 data 竞赛。是的,商店this-&gt;mWakeUp[threadIndex] = false;WorkerThread 中的通知this-&gt;mWaitCondition[threadIndex].notify_all(); 之间存在竞争,但这不是数据竞争。 @Casey 你是对的,std::atomic 保证访问没有竞争条件。但这仅对这个变量有保证,没有上下文(好吧,我们不知道底层的内存屏障)。如果您需要涉及多个变量的“同步于”和“之前发生”关系,则应使用互斥锁等同步原语。 【参考方案1】:

如果线程的 CPU 使用率为零,那么它不会在 while 循环中旋转,而是在 wait() 上阻塞。在 wait() 解除阻塞之前,不会测试循环条件。

检查调试器中的调用堆栈进行验证,暂停位置可能只是指示您的源代码中的返回位置,而不是当前位置。

还要检查 WorkerThread 实例的状态——它们是否正在运行并调用notify_all()?你的调试器线程知道吗?

我不确定我是否理解您的设计或意图,但从表面上看,这对我来说似乎有些过于复杂,并且已经成熟到陷入僵局了。

【讨论】:

在 notify_all() 函数调用之后打印告诉我所有 3 个索引在挂断之前都达到了该点。之后,调试器的位置在 WorkerThread 函数内的 wait() 调用处。带有线程位置的屏幕截图。卡在等待:i.imgur.com/dYcXZAL.png 关于设计:我尝试运行 3 个线程,每帧都工作,我需要等待所有 3 个线程都完成,然后才能继续在主线程上工作。 @anthom 使用 futures 和 promises 会让这个问题很容易解决。不幸的是,std::future 在当前的标准中有几个缺点。也许使用 boost 未来的实现是一个更好的选择。 theboostcpplibraries.com/boost.thread-futures-and-promises, boost.org/doc/libs/1_58_0/doc/html/thread/… @anthom :屏幕截图应直接添加到问题中,而不是评论中的站外链接。您可以更有效地使用调试器;当您可以使用断点检查到达某个位置时,为什么还要插入打印?您的屏幕截图仅显示“自动”选项卡;在这种情况下,调用堆栈和线程选项卡可能更相关。

以上是关于std::thread 在无限循环中随机时间后锁定的主要内容,如果未能解决你的问题,请参考以下文章

std::thread::join 无限期地阻塞在 main 之外

自动测试后安全清理阻塞的 std::thread

C++ 11 std::thread 循环

std::this_thread::sleep_until 时间完全偏离了大约 2 倍,莫名其妙

如何在 Qt 的主事件循环中使用 std::thread?

std::mutex 锁定的顺序