【问题标题】:moveToThread vs deriving from QThread in QtmoveToThread 与从 Qt 中的 QThread 派生
【发布时间】:2018-11-10 08:42:21
【问题描述】:

什么时候moveToThread 比继承QThread 更受欢迎?

This link 表明这两种方法都有效。我应该在什么基础上从这两者中决定使用什么?

【问题讨论】:

  • here 是很好的解释。
  • 这篇文章只是一个观点。没有解释为什么,另一方面,You’re doing it wrong… 解释了问题所在,它是由开发 Qt 的人编写的。基本上我不会相信第一篇文章(互联网上到处都是废话)。

标签: c++ multithreading qt


【解决方案1】:

我将重点介绍这两种方法之间的差异。没有适合所有用例的通用答案,因此最好准确了解它们的含义,然后选择最适合您的情况。

使用 moveToThread()

moveToThread() 用于控制对象的线程亲和性,这基本上意味着设置对象将发出信号的线程(或更好的Qt事件循环)并且它的槽将被执行。

如您链接的文档中所示,这可用于在不同的线程上运行代码,基本上是创建一个 dummy worker,编写代码以在 public slot(在示例中为 doWork() 插槽),然后使用 moveToThread 将其移动到不同的事件循环。

然后,触发连接到该插槽的信号。由于发出信号的对象(示例中的 Controller)位于不同的线程中,并且信号通过排队连接连接到我们的 doWork 方法,因此 doWork 方法将在 worker 中执行线程。

这里的关键是您正在创建一个由工作线程运行的新事件循环。因此,一旦 doWork 槽启动,整个事件循环将一直忙到它退出,这意味着传入的信号将排队。

继承 QThread()

Qt 文档中描述的另一种方法是继承 QThread。在这种情况下,我们会覆盖 QThread::run() 方法的默认实现,该方法会创建一个事件循环,以运行其他东西。

这种方法本身没有任何问题,尽管有几个问题。

首先,编写不安全的代码非常容易,因为run() 方法是该类中唯一实际运行在另一个线程上的方法。

如果作为一个例子,你有一个在构造函数中初始化然后在 run() 方法中使用的成员变量,你的成员在调用者的线程中被初始化,然后在新线程。

任何可以从调用者或内部 run() 调用的公共方法也是如此。

slots 也会从调用者的线程中执行,(除非你像 moveToThread(this) 那样做一些非常奇怪的事情)导致额外的混乱。

所以,这是可能的,但你真的是靠自己,你必须格外注意。

其他方法

当然,这两种方法都有替代方案,具体取决于您的需要。如果您只需要在 GUI 线程运行时在后台运行一些代码,您可以考虑使用QtConcurrent::run()

但是,请记住 QtConcurrent 将使用全局 QThreadPool。如果整个池都忙(意味着池中没有可用的线程),您的代码将不会立即运行。

如果您至少使用 C++11,另一种选择是使用较低级别的 API,例如 std::thread

【讨论】:

    【解决方案2】:

    作为一个起点:两者都不使用。在大多数情况下,您有一个希望异步运行的工作单元。为此使用QtConcurrent::run

    如果您有一个对事件作出反应和/或使用计时器的对象,则它是一个 QObject,它应该是非阻塞的并进入一个线程,可能与其他对象共享。

    这样的对象也可以封装阻塞 API。

    子类化QThread 在实践中从来没有必要。这就像子类化QFileQThread 是一个线程句柄。它包装了系统资源。重载它有点傻。

    【讨论】:

    • Subclassing QThread is never necessary in practice. 如果我不创建 QThread 的子类,那么重新定义 run() 函数的方法是什么?
    • 你还没有谈到“movetothread”。在什么情况下应该首选?子类化 qthread 和 movetothread 都需要编写一个新类。
    • 移动到线程的对象可能具有很多功能。无论如何,所有这些都属于一个单独的类。如果没有,则编写一个独立函数或 lambda 以提交给QtConcurrent::run。子类化QThread 绝不是一个解决方案,因为它绝对是一个奇怪的对象。它驻留在错误的线程中。它并不意味着要被子类化,同时还要为其添加插槽等。
    • @Aquarius_Girl moveToThread 用于确定 QObject 线程亲和力。如果你有类发射信号和接收槽,它们应该在不同的线程中工作,那就是使用 moveToThread,因为在不同线程中调用槽的工作方式与同一个线程不同。
    • @Jeka A QObject 的线程在发出信号时无关紧要。而是使用当前线程。
    【解决方案3】:

    简单的答案是永远。 当您将对象移动到线程时:

    • 为代码编写测试很容易
    • 重构代码很容易(可以使用线程,但不是必须)。
    • 不要将线程的功能与业务逻辑混为一谈
    • 对象生命周期没有问题

    当你继承QThread

    • 写测试比较难
    • 对象清理过程可能会变得非常混乱,从而导致奇怪的错误。

    问题的完整描述来自 Qt 博客:You’re doing it wrong…

    QtConcurrent::run也很方便。

    请记住,默认情况下,当信号从分配给其他线程对象的发送时,插槽会尝试在线程之间跳转。详情见Qt::ConnectionType的文档。

    【讨论】:

      【解决方案4】:

      QThread是低级线程抽象,先看高级APIQtConcurrent模块和QRunnable

      如果这些都不适合你,那么阅读this old article,它告诉你应该如何使用QThread。将此线程中执行的线程和任务视为单独的对象,不要将它们混合在一起。

      因此,如果您需要编写自定义、特定或扩展的线程包装器,那么您应该继承 QThread。

      如果您有带有信号和槽的 QObject 派生类,则在其上使用 moveToThread。

      在其他情况下使用 QtConcurrent、QRunnable 和 QThreadPoll。

      【讨论】:

        猜你喜欢
        • 2012-06-17
        • 1970-01-01
        • 2013-10-15
        • 2013-05-23
        • 2021-07-17
        • 2012-12-18
        • 2013-12-17
        • 1970-01-01
        • 2017-01-28
        相关资源
        最近更新 更多