moveToThread vs从Qt中的QThread派生

时间:2018-05-31 10:48:29

标签: c++ multithreading qt

什么时候应该moveToThread优先于子类QThread

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

4 个答案:

答案 0 :(得分:16)

我会关注两种方法之间的差异。没有适用于所有用例的一般答案,因此最好准确理解他们选择最适合您案例的内容。

使用moveToThread()

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

如您链接的文档中所示,这可用于在不同的线程上运行代码,基本上创建虚拟工作者,编写代码以在公共插槽中运行(在示例中 doWork()插槽),然后使用 moveToThread 将其移动到其他事件循环。

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

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

子类化QThread()

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

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

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

例如,如果您有一个在构造函数中初始化的成员变量,然后在 run()方法中使用,那么您的成员将在调用者的线程中初始化,然后用于新线程。

可以从调用者或内部run()调用的任何公共方法的相同故事。

此外,还会从调用方的线程执行插槽(除非你做了一些非常奇怪的事情,因为 moveToThread(this))会导致额外的混乱。

所以,这是有可能的,但是你真的对这种方法有所了解,你必须特别注意。

其他方法

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

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

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

答案 1 :(得分:8)

作为起点:不要使用。在大多数情况下,您有一个工作单元,您希望以异步方式运行。请使用QtConcurrent::run

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

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

在实践中,从不需要子类化QThread。它就像是对QFile进行子类化。 QThread是一个线程句柄。它包装了一个系统资源。重载它有点傻。

答案 2 :(得分:3)

QThread是低级线程抽象,首先看一下高级API QtConcurrent模块和QRunnable

如果这些内容中没有任何内容适合您,请阅读this old article,它会说明您应该如何使用QThread。将此线程中执行的线程和任务视为单独的对象,不要将它们混合在一起。

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

如果你有带信号和槽的QObject派生类,那么就在它上面使用moveToThread。

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

答案 3 :(得分:0)

简单的答案总是如此。 将对象移动到线程时:

  • 编写代码测试很容易
  • 很容易重构代码(你可以使用线程,但你不必)。
  • 您不会将线程功能与业务逻辑混合
  • 对象有效期没有问题

当您继承QThread

  • 编写测试更难
  • 对象清理过程会变得非常混乱,导致奇怪的错误。

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

QtConcurrent::run也非常方便。

请记住,默认情况下,当从其他线程对象发送信号时,插槽会尝试在踏板之间跳转。有关详细信息,请参阅Qt::ConnectionType的文档。