我是多线程编程的新手。我用Qt编写了这个简单的多线程程序。但是当我运行这个程序时它冻结了我的GUI,当我点击我的寡妇时,它会回应你的程序没有响应。 这是我的widget类。我的线程开始计算一个整数,当这个数字可以被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);
}
}
谢谢!
答案 0 :(得分:9)
问题在于你的线程代码产生了一个事件风暴。循环计数非常快 - 如此之快,以至于每1000次迭代发出一个信号这一事实非常重要。在现代CPU上,执行1000个整数分区大约需要10微秒IIRC。如果环路是唯一的限制因素,那么您将以每秒约100,000次的峰值速率发射信号。事实并非如此,因为其他因素限制了表现,我们将在下面讨论。
让我们了解当您在接收器QObject
所在的不同线程中发出信号时会发生什么。信号打包在QMetaCallEvent
中并发布到接收线程的事件队列中。在接收线程中运行的事件循环(这里是GUI线程)使用QAbstractEventDispatcher
的实例对这些事件进行操作。每个QMetaCallEvent
都会调用已连接的广告位。
接收GUI线程的事件队列的访问由QMutex
序列化。在Qt 4.8及更高版本中,QMutex实现获得了很好的加速,因此每个信号发射导致锁定队列互斥的事实不太可能成为问题。唉,事件需要在工作线程的堆上分配,然后在GUI线程中解除分配。如果线程碰巧在不同的内核上执行,那么当快速连续发生时,许多堆分配器的性能都很差。
最大的问题出在GUI线程中。似乎有一堆隐藏的O(n ^ 2)复杂度算法!事件循环必须处理10,000个事件。这些事件很可能非常快速地传递,最终在事件队列中的连续块中。事件循环必须在处理其他事件之前处理所有事件。调用插槽时会发生许多昂贵的操作。不仅QMetaCallEvent
从堆中释放,而且标签调度update()
(重绘),并且在内部将可压缩事件发布到事件队列。在最坏的情况下,可压缩事件发布必须遍历整个事件队列。这是一个潜在的O(n ^ 2)复杂行为。在实践中可能更重要的另一个此类操作是进度条的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线程的大量工作。即使您的代码看起来无害。
我认为让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#提供了对编程生成代码的充分支持,但所有这些......
答案 1 :(得分:2)
您实现线程的方式,它没有自己的事件循环(因为它不调用exec()
)。我不确定run()
中的代码是否实际在您的线程内或GUI线程内执行。
通常你不应该继承QThread
。您可能这样做是因为您阅读了Qt文档,遗憾的是仍然建议继承QThread
- 尽管开发人员很久以前曾写过a blog entry声明您不应该继承QThread
。不幸的是,他们仍然没有适当更新文档。
我建议在Qt Blog上阅读"You're doing it wrong",然后使用answer by "Kari"作为如何设置基本多线程系统的示例。
答案 2 :(得分:0)
But when I run this program it freezes my GUI and when I click inside my window,
it responds that your program is not responding.
是的,因为IMO你正在在线程中做太多工作它耗尽CPU 。当进程在处理应用程序事件队列请求时没有显示进度时,通常会弹出program is not responding
消息。在你的情况下会发生这种情况。
所以在这种情况下你应该找到一种划分工作的方法。仅举例来说,线程以 100 的块运行并重复线程直到它完成 10000000 。
另外,当您执行冗长的操作时,您应该查看QCoreApplication::processEvents()
。