【问题标题】:QT Application freezesQT 应用程序冻结
【发布时间】:2019-11-04 20:43:33
【问题描述】:

我们开发了一个集成了摄像头和微控制器的系统。 GUI 显示来自相机的图像和来自微控制器的串行计数,我们使用串行线程来轮询来自微控制器的数据并将信号发送到 GUI 以显示它,并且我们使用单独的线程来捕获图像并将其传递给 main线。 应用程序的问题是当系统处于空闲状态时,GUI 冻结,我们必须重新启动应用程序才能开始工作(空闲的意思是,用户没有租用任何按钮并且计数和图像不断进入)。 这里要注意的最重要的事情是 GUI 冻结问题在这里不一致。安装了几个系统,在某些地方,冻结(无响应)问题每 2/3 周出现一次,在某些地方,每 2 天出现一次。等待应用程序响应没有帮助。

我的主要问题是 GUI 冻结的主要原因是什么,是否有任何检查要在串行线程和图像捕获线程上实施以避免不必要的数据排放。

【问题讨论】:

  • 您是否在捕获线程上编写了重采样器,以便主线程不会被传入数据淹没?你监控输入率了吗?
  • @ArneJ 我不确定我是否做到了,你能告诉我如何检查吗?
  • @vallabh 对于初学者,每次捕获线程接收数据时递增一个整数。在另一个线程中,运行一个休眠一秒钟然后打印计数器的循环。现在在渲染(主)线程中做同样的事情。如果您的应用程序稳定,则即使经过长时间的会话,数字也应始终保持不变。
  • 如果消费者线程没有足够快地吸收数据,则可能会出现 GUI 冻结问题。 在这种情况下,上述想法也可以提供证明作为修复。生产者(甚至消费者)可能会检查有多少挂起的图像尚未被消费并跳过图像以防万一。
  • @vallabh 是个坏主意,那么您仍在充斥生产者/捕获线程。控制输入​​速率或重新采样(丢弃一些帧)。

标签: c++ qt opencv serial-port qtgui


【解决方案1】:

听起来您遇到了并发冲突,它不一定会导致崩溃,直到事情运行足够长的时间才能最终达到在正确时间发生的事件的神奇组合以阻止事情。

您的应用中有三个线程:一个 GUI 线程、一个串行线程和一个摄像头线程。串行和摄像头线程从设备收集数据,然后将它们传递给 GUI 线程进行显示。我认为串口和摄像头线程之间不共享任何数据,因此不存在出现问题的风险。

您如何将数据从串行和摄像头线程传递到 GUI 线程?这就是您可能遇到问题的地方。

大多数复杂的数据结构和 Qt 类都不是线程安全的,也就是说,它们绝不能同时从两个或多个线程同时读取和写入。

以下是线程间安全传递数据的一些策略:

  1. 整数在 CPU 的指令集级别是原子的,因此您可以安全地从多个线程而不会发生任何不一致的状态。您必须使用 C++ 的 std::atomic 模板声明此类变量,以确保编译器不会执行破坏原子性的优化。

    任何比整数更大/更复杂的东西都有这样的风险:一个线程将一半的数据写入内存,而另一个线程同时读取了一半写入的数据,从而导致非常意外的结果,经常使您的应用程序崩溃或获取某些东西陷入了无限循环。

  2. Qt 中的信号和槽是线程安全的。在线程之间传递复杂数据的一种方法是emit 一个线程中的数据并让另一个线程中的slot 接收该数据。在这种情况下,Qt 会在后台为您处理任何并发问题。这里唯一的问题是,如果数据的消费者线程不能足够快地吸收数据,Qt 的事件队列将被太多数据塞满,最终你的应用程序会因为新的 GUI 事件(如鼠标点击、重绘事件等)无法再通过阻塞的事件队列。

  3. 您可以使用QMutex 来确保一次只有一个线程在读取或写入复杂的数据结构。 QMutex 允许您在一个或多个线程中阻止/停止执行,而单个线程“持有”互斥锁并允许它执行,对数据进行工作,而不会有其他线程接触该数据的风险。当一个线程完成后,它“释放”互斥锁,然后允许其他线程之一“持有”互斥锁,恢复其执行,以便它可以处理数据。

就测试而言,您的应用因并发违规而崩溃的几率通常会随着数据流率的提高而上升。如果您可以人为地增加传递给 GUI 线程的相机帧率和“串行计数”,您将能够更快地重现您的应用程序崩溃。成功解决并发问题后,您应该能够向系统充斥数据,并且永远不会崩溃。

【讨论】:

  • 现在我在线程中使用信号槽机制,正如你所说,如果消费者线程没有足够快地吸收数据,那么可能会出现 GUI 冻结问题。什么是我在图像捕获线程中添加延迟并每隔一秒发出一次数据
  • 整数在 CPU 的指令集级别是原子的,因此您可以安全地读取和写入整数 这是一个很大的错误(我曾经有过自己的想法)。你忽略了,例如整数可以在 CPU 寄存器中更新或完美缓存。因此,两个线程可能对同一个变量使用两个“化身”。此外,编译器可能会重新排序可能会干扰单独线程中的预期使用的分配。因此,std 提供了std::atomic
  • @vallabh,因为你要处理这么多数据,如果是我,我会远离使用信号/槽机制。即使您将将数据推送到事件队列的相机线程减慢到每秒一帧,这仍然是大量数据。操作系统可以并且将暂停您的 GUI 线程超过一秒(例如在大量磁盘 I/O 期间),从而导致事件队列阻塞。您真正需要的是这样一种设计,当用户太忙而无法完全处理流量时,可以随意丢弃一些帧。
  • @Scheff,好点子!我在答案中添加了关于 std::atomic 的注释。有时,作为一名汇编语言程序员,我在更简单的架构上思考太多,而忘记了 C/C++ 编译器所做的所有奇怪优化以及现代多核 CPU 中发生的所有花哨的缓存一致性。 :)
  • 编辑太晚了,但我应该说“当消费者太忙而无法完全处理流量时,生产者可以任意丢弃一些帧。”根据我充斥事件队列的经验,一旦你把它塞满,似乎没有任何恢复——你的应用程序崩溃了。这就是为什么我不喜欢固定(虽然较慢)生产者推送率的想法,因为如果操作系统延迟消费者足够长的时间,它仍然有可能使应用程序崩溃。当您检测到消费者刚刚开始落后时,在生产者处丢帧(跳过发射)似乎更好。
猜你喜欢
  • 2018-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-09
  • 2013-09-26
  • 2014-09-29
  • 2016-02-10
  • 1970-01-01
相关资源
最近更新 更多