QThread执行冻结了我的GUI [英] QThread execution freezes my GUI

查看:77
本文介绍了QThread执行冻结了我的GUI的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是多线程编程的新手.我用Qt编写了这个简单的多线程程序.但是,当我运行该程序时,它冻结了我的GUI,并且当我在寡妇中单击时,它响应说您的程序没有响应. 这是我的小部件类.我的线程开始计算一个整数,并在该数字可被1000整除时发出该整数.在我的小部件中,我只是用信号插槽机制捕获了这个数字,并在标签和进度栏中显示了它.

I'm new to multithread programming. I wrote this simple multi thread program with Qt. But when I run this program it freezes my GUI and when I click inside my widow, it responds that your program is not responding . Here is my widget class. My thread starts to count an integer number and emits it when this number is dividable by 1000. In my widget simply I catch this number with signal-slot mechanism and show it in a label and a progress bar.

   Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    MyThread *th = new MyThread;
    connect( th, SIGNAL(num(int)), this, SLOT(setNum(int)));
    th->start();
}


void Widget::setNum(int n)
{
    ui->label->setNum( n);
    ui->progressBar->setValue(n%101);
}

这是我的线程run()函数:

and here is my thread run() function :

void MyThread::run()
{
    for( int i = 0; i < 10000000; i++){
        if( i % 1000 == 0)
            emit num(i);
    }
}

谢谢!

推荐答案

问题出在线程代码产生事件风暴上.循环计数非常快-如此之快,以至于每1000次迭代发出一个信号这一事实几乎没有意义.在现代CPU上,进行1000个整数除法需要大约10微秒的IIRC.如果环路是唯一的限制因素,那么您将以大约每秒100,000的峰值速率发射信号.并非如此,因为性能受其他因素的限制,我们将在下面讨论.

The problem is with your thread code producing an event storm. The loop counts very fast -- so fast, that the fact that you emit a signal every 1000 iterations is pretty much immaterial. On modern CPUs, doing a 1000 integer divisions takes on the order of 10 microseconds IIRC. If the loop was the only limiting factor, you'd be emitting signals at a peak rate of about 100,000 per second. This is not the case because the performance is limited by other factors, which we shall discuss below.

让我们了解当您在与接收器QObject所在的线程不同的线程中发射信号时会发生什么.信号打包在QMetaCallEvent中,并发布到接收线程的事件队列中.在接收线程(这里为GUI线程)中运行的事件循环使用QAbstractEventDispatcher的实例对这些事件进行操作.每个QMetaCallEvent都会导致对连接的插槽的调用.

Let's understand what happens when you emit signals in a different thread from where the receiver QObject lives. The signals are packaged in a QMetaCallEvent and posted to the event queue of the receiving thread. An event loop running in the receiving thread -- here, the GUI thread -- acts on those events using an instance of QAbstractEventDispatcher. Each QMetaCallEvent results in a call to the connected slot.

对接收方GUI线程的事件队列的访问由QMutex序列化.在Qt 4.8和更高版本上,QMutex实现获得了不错的加速,因此,每个信号发射都会导致锁定队列互斥体这一事实不太可能成为问题. las,需要在工作线程中的堆上分配事件,然后在GUI线程中将其释放.如果线程恰好在不同的内核上执行,那么许多快速连续发生的堆分配器的性能就会很差.

The access to the event queue of the receiving GUI thread is serialized by a QMutex. On Qt 4.8 and newer, the QMutex implementation got a nice speedup, so the fact that each signal emission results in locking of the queue mutex is not likely to be a problem. Alas, the events need to be allocated on the heap in the worker thread, and then deallocated in the GUI thread. Many heap allocators perform quite poorly when this happens in quick succession if the threads happen to execute on different cores.

最大的问题来自GUI线程.似乎有一堆隐藏的O(n ^ 2)复杂度算法!事件循环必须处理10,000个事件.这些事件很可能会非常快地交付,并最终在事件队列中的连续块中结束.事件循环必须先处理所有这些事件,然后才能处理其他事件.调用插槽时,会发生许多昂贵的操作.不仅从堆中释放了QMetaCallEvent,而且标签还计划了update()(重绘),并且在内部将可压缩事件发布到事件队列中.在最坏的情况下,可压缩事件发布必须遍历整个事件队列.那是一个潜在的O(n ^ 2)复杂性动作.另一个这样的操作(实际上在实践中可能更重要)是进度条的setValue在内部调用QApplication::processEvents().这可以递归地调用您的插槽以传递事件队列中的后续信号.您所做的工作比您想象的要多,这将锁定GUI线程.

The biggest problem comes in the GUI thread. There seems to be a bunch of hidden O(n^2) complexity algorithms! The event loop has to process 10,000 events. Those events will be most likely delivered very quickly and end up in a contiguous block in the event queue. The event loop will have to deal with all of them before it can process further events. A lot of expensive operations happen when you invoke your slot. Not only is the QMetaCallEvent deallocated from the heap, but the label schedules an update() (repaint), and this internally posts a compressible event to the event queue. Compressible event posting has to, in worst case, iterate over entire event queue. That's one potential O(n^2) complexity action. Another such action, probably more important in practice, is the progressbar's setValue internally calling QApplication::processEvents(). This can, recursively call your slot to deliver the subsequent signal from the event queue. You're doing way more work than you think you are, and this locks up the GUI thread.

插入您的广告位,看看是否以递归方式调用它.一种快速而肮脏的方法是

Instrument your slot and see if it's called recursively. A quick-and-dirty way of doing it is

void Widget::setNum(int n)
{
  static int level = 0, maxLevel = 0;
  level ++;
  maxLevel = qMax(level, maxLevel);
  ui->label->setNum( n);
  ui->progressBar->setValue(n%101);
  if (level > 1 && level == maxLevel-1) {
    qDebug("setNum recursed up to level %d", maxLevel);
  }
  level --;
}

冻结您的GUI线程的不是QThread的执行,而是您使GUI线程进行的大量工作.即使您的代码看起来很纯洁.

What is freezing your GUI thread is not QThread's execution, but the huge amount of work you make the GUI thread do. Even if your code looks innocuous.

我认为让QProgressBar::setValue调用processEvents()是一个非常糟糕的主意.它仅鼓励人们以破旧的方式编写代码(连续运行代码,而不是短时间运行到完成的代码).由于processEvents()呼叫可以递归到呼叫者,因此setValue成为非角色角色,并且可能非常危险.

I think it was a very bad idea to have QProgressBar::setValue invoke processEvents(). It only encourages the broken way people code things (continuously running code instead of short run-to-completion code). Since the processEvents() call can recurse into the caller, setValue becomes a persona-non-grata, and possibly quite dangerous.

如果要以连续样式进行编码但仍保留即点即用"语义,则可以使用C ++处理这种方式.一个只是利用预处理器,例如代码请参见我的其他答案.

If one wants to code in continuous style yet keep the run-to-completion semantics, there are ways of dealing with that in C++. One is just by leveraging the preprocessor, for example code see my other answer.

另一种方法是使用表达式模板来获取C ++编译器来生成代码想.您可能想在此处利用模板库-提升精神有一个不错的起点即使您没有编写解析器也可以重用的实现.

Another way is to use expression templates to get the C++ compiler to generate the code you want. You may want to leverage a template library here -- Boost spirit has a decent starting point of an implementation that can be reused even though you're not writing a parser.

Windows Workflow Foundation 也解决了如何解决编写顺序样式代码,但可以将其作为简短的运行完成片段来运行.他们诉诸于以XML指定控制流.显然,没有直接使用标准C#语法的直接方法.他们仅将其作为数据结构提供,即a-la JSON.如果愿意的话,在Qt中实现XML和基于代码的WF足够简单.尽管.NET和C#都提供了足够的支持以编程方式生成代码...

The Windows Workflow Foundation also tackles the problem of how to write sequential style code yet have it run as short run-to-completion fragments. They resort to specifying the flow of control in XML. There's apparently no direct way of reusing standard C# syntax. They only provide it as a data structure, a-la JSON. It'd be simple enough to implement both XML and code-based WF in Qt, if one wanted to. All that in spite of .NET and C# providing ample support for programmatic generation of code...

这篇关于QThread执行冻结了我的GUI的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆