首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用昂贵操作的线程减慢UI线程- Windows 10,C++

使用昂贵操作的线程减慢UI线程- Windows 10,C++
EN

Stack Overflow用户
提问于 2021-05-30 02:31:28
回答 1查看 161关注 0票数 0

问题:在我正在处理的Windows 10应用程序中,有两个线程,一个UI线程(代码中称为呈现线程)和后台一个工作线程(代码中称为模拟线程)。只要几秒钟左右,后台线程就必须执行一个非常昂贵的操作,其中涉及到分配大量内存。由于某些原因,当此操作发生时,UI线程会延迟一秒钟,并变得没有响应(这在应用程序中被看作是照相机在给定摄像机移动输入时不移动一秒钟)。

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

我尝试过的我删除了两个线程之间的所有通信,所以没有互斥或类似的东西(除非有一些我不知道的隐含的东西)。我还尝试将UI线程设置为比后台线程更高的优先级。这两件事都没有帮助。

我注意到了一些事情:虽然UI线程滞后了一会儿,但我的机器上运行的其他应用程序和以往一样具有响应性。繁重的操作似乎只影响到这一个过程。另外,如果我减少了被分配的内存量,它就可以缓解这个问题(但是,为了使应用程序按我的意愿工作,它需要能够完成这个分配)。

这个问题:,我的问题是双重的.首先,我想了解为什么会发生这种情况,因为这似乎违背了我对多线程应该如何工作的理解。其次,对于如何解决这个问题,您有什么建议或想法吗?这样UI就不会滞后了。

缩写代码:--注意Timeeline.h main.cpp中关于历元的注释

代码语言:javascript
复制
#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;
}

simulator.cpp

代码语言:javascript
复制
// abbreviated
void Simulator::run() {
    while (true) {
        // abbreviated
        timeline->push(latestState);
    }
}
// abbreviated

timeline.h

代码语言:javascript
复制
#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

timeline.cpp

代码语言:javascript
复制
#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:循环调用演示者::update()和一些OpenGL呈现任务。

Presenter.cpp

代码语言:javascript
复制
// 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

有什么帮助/建议吗?

EN

回答 1

Stack Overflow用户

发布于 2021-05-30 21:00:27

我终于弄明白了!

因此,事实证明,new操作符在C++中是threadsafe,这意味着一旦它启动,它必须在任何其他线程能够完成任何操作之前完成。为什么在我的案子里这是个问题?当一个Epoch被初始化时,它必须初始化一个1024 WorldStates的数组,每个数组都有10,000个需要初始化的CellStates,其中每个都有一个需要初始化的16项数组,所以我们最终得到了超过100,000,000个需要初始化的对象,然后new操作符才能返回。这花费了足够长的时间,使得UI在等待时会打嗝。

解决方案是创建一个工厂函数,该函数将逐段构建epoch片段,一次构建一个构造函数,然后将它们组合在一起,并返回一个指向新时代的指针。

timeline.h

代码语言:javascript
复制
    #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

timeline.cpp

代码语言:javascript
复制
    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;
    }
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/67757191

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档