【问题标题】:Thread with expensive operations slows down UI thread - Windows 10, C++具有昂贵操作的线程会减慢 UI 线程 - Windows 10、C++
【发布时间】:2021-08-17 18:48:02
【问题描述】:

问题:我正在处理的 Windows 10 应用程序中有两个线程,一个 UI 线程(在代码中称为渲染线程)和一个后台工作线程(称为在代码中模拟线程)。每隔几秒钟左右,后台线程就必须执行一项非常昂贵的操作,该操作涉及分配大量内存。出于某种原因,当此操作发生时,UI 线程会滞后一秒并变得无响应(这在应用程序中被视为在给出相机移动输入时相机没有移动一秒钟)。

也许我对线程在 Windows 上的工作方式存在误解,但我不知道这是应该发生的事情。我的印象是您使用单独的 UI 线程正是出于这个原因:在其他线程执行更多时间密集型操作时保持它的响应速度。

我尝试过的事情:我已经删除了两个线程之间的所有通信,所以没有互斥锁或类似的东西(除非 Windows 隐含了我没有这样做的东西意识到)。我还尝试将 UI 线程设置为比后台线程更高的优先级。这些都没有帮助。

我注意到的一些事情:虽然 UI 线程滞后了片刻,但在我的机器上运行的其他应用程序仍然像以往一样响应。繁重的操作似乎只影响这一个过程。此外,如果我减少分配的内存量,它会缓解问题(但是,要让应用程序按我想要的方式工作,它需要能够进行这种分配)。

问题:我的问题有两个。首先,我想了解为什么会这样,因为这似乎违背了我对多线程应该如何工作的理解。其次,你有什么建议或想法来解决这个问题并得到它,这样 UI 就不会滞后。

缩写代码:注意timeline.h中关于epochs的注释 main.cpp

#include "Renderer/Headers/Renderer.h"
#include "Shared/Headers/Timeline.h"
#include "Simulator/Simulator.h"

#include <iostream>
#include <Windows.h>


unsigned int __stdcall renderThread(void* timelinePtr);
unsigned int __stdcall simulateThread(void* timelinePtr);

int main() {
    Timeline timeline;

    HANDLE renderHandle = (HANDLE)_beginthreadex(0, 0, &renderThread, &timeline, 0, 0);
    if (renderHandle == 0) {
        std::cerr << "There was an error creating the render thread" << std::endl;
        return -1;
    }
    SetThreadPriority(renderHandle, THREAD_PRIORITY_HIGHEST);

    HANDLE simulateHandle = (HANDLE)_beginthreadex(0, 0, &simulateThread, &timeline, 0, 0);
    if (simulateHandle == 0) {
        std::cerr << "There was an error creating the simulate thread" << std::endl;
        return -1;
    }
    SetThreadPriority(simulateHandle, THREAD_PRIORITY_IDLE);

    WaitForSingleObject(renderHandle, INFINITE);
    WaitForSingleObject(simulateHandle, INFINITE);
    return 0;
}

unsigned int __stdcall renderThread(void* timelinePtr) {
    Timeline& timeline = *((Timeline*)timelinePtr);

    Renderer renderer = Renderer(timeline);
    renderer.run();
    return 0;
}

unsigned int __stdcall simulateThread(void* timelinePtr) {
    Timeline& timeline = *((Timeline*)timelinePtr);

    Simulator simulator(timeline);
    simulator.run();
    return 0;
}

模拟器.cpp

// abbreviated
void Simulator::run() {
    while (true) {
        // abbreviated
        timeline->push(latestState);
    }
}
// abbreviated

时间线.h

#ifndef TIMELINE_H
#define TIMELINE_H

#include "WorldState.h"
#include <mutex>
#include <vector>

class Timeline {
public:
    Timeline();
    bool tryGetStateAtFrame(int frame, WorldState*& worldState);
    void push(WorldState* worldState);
private:

    // The concept of an Epoch was introduced to help reduce mutex conflicts, but right now since the threads are disconnected, there should be no mutex locks at all on the UI thread. However, every 1024 pushes onto the timeline, a new Epoch must be created. The amount of slowdown largely depends on how much memory the WorldState class takes. If I make WorldState small, there isn't a noticable hiccup, but when it is large, it becomes noticeable.  
    class Epoch {
    public:
        static const int MAX_SIZE = 1024;

        void push(WorldState* worldstate);
        int getSize();
        WorldState* getAt(int index);
    private:
        int size = 0;
        WorldState states[MAX_SIZE];
    };
    Epoch* pushEpoch;

    std::mutex lock;
    std::vector<Epoch*> epochs;
};

#endif // !TIMELINE_H

时间线.cpp

#include "../Headers/Timeline.h"

#include <iostream>

Timeline::Timeline() {
    pushEpoch = new Epoch();
}

bool Timeline::tryGetStateAtFrame(int frame, WorldState*& worldState) {
    if (!lock.try_lock()) {
        return false;
    }
    if (frame >= epochs.size() * Epoch::MAX_SIZE) {
        lock.unlock();
        return false;
    }
    worldState = epochs.at(frame / Epoch::MAX_SIZE)->getAt(frame % Epoch::MAX_SIZE);
    lock.unlock();
    return true;
}

void Timeline::push(WorldState* worldState) {
    pushEpoch->push(worldState);
    if (pushEpoch->getSize() == Epoch::MAX_SIZE) {
        lock.lock();
        epochs.push_back(pushEpoch);
        lock.unlock();
        pushEpoch = new Epoch();
    }
}

void Timeline::Epoch::push(WorldState* worldState) {
    if (this->size == this->MAX_SIZE) {
        throw std::out_of_range("Pushed too many items to Epoch without clearing");
    }
    this->states[this->size] = *worldState;
    this->size++;
}

int Timeline::Epoch::getSize() {
    return this->size;
}

WorldState* Timeline::Epoch::getAt(int index) {
    if (index >= this->size) {
        throw std::out_of_range("Tried accessing nonexistent element of epoch");
    }
    return &(this->states[index]);
}

Renderer.cpp:循环调用 Presenter::update() 和一些 OpenGL 渲染任务。

Presenter.cpp

// abbreviated
void Presenter::update() {
    camera->update();
    // timeline->tryGetStateAtFrame(Time::getFrames(), worldState); // Normally this would cause a potential mutex conflict, but for now I have it commented out. This is the only place that anything on the UI thread accesses timeline.
}
// abbreviated

有什么帮助/建议吗?

【问题讨论】:

    标签: c++ windows multithreading user-interface opengl


    【解决方案1】:

    我终于弄明白了!

    事实证明,C++ 中的new 运算符是线程安全的,这意味着一旦它启动,它必须在任何其他线程执行任何操作之前完成。为什么这对我来说是个问题?好吧,当一个 Epoch 被初始化时,它必须初始化一个由 1024 个 WorldStates 组成的数组,每个 WorldStates 都有 10,000 个 CellStates 需要初始化,而 这些 中的每一个都有一个包含 16 个项的数组需要被初始化,所以我们最终需要在 new 操作符返回之前初始化超过 100,000,000 个对象。这花费了足够长的时间,导致 UI 在等待时出现打嗝。

    解决方案是创建一个工厂函数,该函数将零碎地构建 Epoch 的各个部分,一次一个构造函数,然后将它们组合在一起并返回一个指向新 epoch 的指针。

    时间线.h

    
        #ifndef TIMELINE_H
        #define TIMELINE_H
        
        #include "WorldState.h"
        #include <mutex>
        #include <vector>
        
        class Timeline {
        public:
            Timeline();
            bool tryGetStateAtFrame(int frame, WorldState*& worldState);
            void push(WorldState* worldState);
        private:
        
            class Epoch {
            public:
                static const int MAX_SIZE = 1024;
                static Epoch* createNew();
        
                void push(WorldState* worldstate);
                int getSize();
                WorldState* getAt(int index);
            private:
                Epoch();
        
                int size = 0;
                WorldState* states[MAX_SIZE];
            };
            Epoch* pushEpoch;
        
            std::mutex lock;
            std::vector<Epoch*> epochs;
        };
        
        #endif // !TIMELINE_H
    
    

    时间线.cpp

    
        Timeline::Epoch* Timeline::Epoch::createNew() {
            Epoch* epoch = new Epoch();
            for (unsigned int i = 0; i < MAX_SIZE; i++) {
                epoch->states[i] = new WorldState();
            }
            return epoch;
        }
    
    

    【讨论】:

      猜你喜欢
      • 2014-06-09
      • 1970-01-01
      • 1970-01-01
      • 2012-05-17
      • 2017-12-08
      • 2012-12-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多