gpt4 book ai didi

multithreading - QThread执行卡住了我的GUI

转载 作者:行者123 更新时间:2023-12-03 22:44:12 25 4
gpt4 key购买 nike

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

   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()函数:
void MyThread::run()
{
for( int i = 0; i < 10000000; i++){
if( i % 1000 == 0)
emit num(i);
}
}

谢谢!

最佳答案

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

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

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

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

检测一下插槽,看看是否递归调用了它。一种快速而肮脏的方法是

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线程完成的大量工作。即使您的代码看起来无害。

有关processEvents和“运行完成”代码的旁注

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

如果要以连续样式编写代码,但又要保留“即插即用”语义,则可以使用C++处理这种情况。一种只是利用预处理器,例如代码请参见 my other answer

另一种方法是使用 expression templates获取C++编译器以生成所需的代码。您可能要在此处利用模板库- Boost spirit具有实现的不错的起点,即使您没有编写解析器,也可以重用该实现。

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

关于multithreading - QThread执行卡住了我的GUI,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11426335/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com