QThread vs std::thread

QThread vs std::thread

本文关键字:thread std vs QThread      更新时间:2023-10-16

我在"pthread vs std::thread"answers"QThread vs pthread"上看到了不同的主题,但在"std::thread vs QThread"上没有。

我必须编写一个软件来驱动3D打印机,需要使用线程。会有一个线程不断检查安全性,另一个线程执行打印过程,一些线程分别驱动每个硬件组件(运动,喷射,…),等等。该程序是在Windows环境下使用c++ 11/Qt开发的。

首先我想使用QThread,但是在我看来,QThread不允许你做std::thread那么多的事情,例如,在阅读Anthony Williams的"c++并发操作"时,我看到有可能要求std::thread执行来自另一个线程的函数,通过做一些像std::thread t1(&Class::function, this, ...);这样的事情,这似乎不可能与QThread。

我最想要的机制是,如果我想让一个函数在当前线程或另一个线程中执行,可以用一种方式来表示。

你会选择哪一个,为什么?

QThread不仅仅是一个线程,它也是一个线程管理器。如果你想让你的线程玩Qt,那么QThread是要走的路。Qt是事件驱动的,就像大多数现代编程一样。这比"让一个线程运行一个函数"要复杂和灵活一些。

在Qt中,您通常会与QThread一起创建工作人员,将工作人员移动到该线程,然后由该工作人员对象的事件系统调用的每个函数将在该工作人员对象具有亲和力的线程中执行。

所以你可以封装你的功能在不同的工作对象,说SafetyChecker, Printer, ServoDriver, JetDriver等,创建每个实例,移动到一个专用的线程,你设置。您仍然可以调用将"阻塞"而不是使用细粒度事件的函数,并使用原子或互斥体进行线程间同步。这没有什么问题,只要你不阻塞主/gui线程。

您可能不希望使用事件驱动的打印机代码,因为在多线程场景中,这将涉及排队连接,这比直接连接甚至虚拟调度稍微慢一些。因此,如果您将多线程设置得过于细粒度,那么实际上可能会遇到巨大的性能影响。

也就是说,使用Qt的非gui的东西有它自己的优点,它可以让你更容易地做一个更干净、更灵活的设计,如果你正确地实现的话,你仍然会得到多线程的好处。您仍然可以使用事件驱动的方法来管理整个事情,这将比仅使用std::thread要容易得多,CC_9是一个低级得多的结构。您可以使用事件驱动的方法来设置、配置、监视和管理设计,而关键部分可以在辅助线程中的阻塞函数中执行,以尽可能低的同步开销实现细粒度控制。

澄清一下——这个答案并不关注异步任务的执行,因为其他两个答案已经关注了,而且正如我在评论中提到的,异步任务并不是真正用于控制应用程序的。它们适合执行小型任务,这些任务所花费的时间仍然比阻塞主线程所需的时间要长。作为推荐的指导方针,所有耗时超过25毫秒的内容都最好异步执行。而打印可能需要几分钟甚至几个小时,并且需要连续运行并行和同步的控制功能。异步任务不会给你控制应用程序的性能、延迟和顺序保证。

std::threadQThread的主要问题是它做了它在tin上所说的:为您创建一个线程,一个可能只做一件事的线程。使用std::thread"并发"运行一个函数是非常浪费的:线程是昂贵的资源,所以创建一个线程来运行某个函数通常是多余的。

虽然std::thread t1(&Class::function, this, ...)看起来很好,但它通常是一种过早的悲观,并且将其作为"同时做事"的某种通用方式是我认为错误的。你可以做得更好。

  1. 如果你想在工作线程中同时运行一个函数/函子/方法,请使用QtConcurrent::runstd::async

    QtConcurrent::run默认使用默认线程池,您也可以传递您自己的QThreadPool实例。典型的情况是使用默认线程池来处理cpu密集型任务,例如计算、图像转换、渲染等,并使用专用的、更大的I/O线程池来执行由于被迫使用的api的限制而阻塞的操作(例如,许多数据库库只提供阻塞api,因为它们的设计从根本上被破坏了)。例子:

    // interface
    QThreadPool * ioPool();
    // implementation
    Q_GLOBAL_STATIC(QThreadPool, ioPool_impl);
    QThreadPool * ioPool() { return ioPool_impl; }
    
  2. 如果你想让QObject生活在另一个线程中(可能与其他对象共同居住),使用QThread然后使用moveToThread将你的对象移动到该线程。

    从工作线程发出信号以线程安全的方式将数据传递给主线程是一种习惯用法。例如,假设你想有一个响应式GUI,并希望在工作线程中从磁盘加载图像:

    class MyWidget : public QWidget {
      QLabel m_label;
      ...
      Q_SIGNAL void setImage(const QImage &);
     public:
      MyWidget() {
       ...
       connect(MyWidget, &MyWidget::setImage, this, [this](const QImage & image){
        m_label.setPixmap(QPixmap::fromImage(image));
       });
       QtConcurrent::run(ioPool(), [this]{ setImage({"/path/to/image.png"});  });
      }
    };
    

如果您想将线程集成到Qt系统中(例如必须发出信号或连接到某些插槽)

虽然QThread的布局仍然是这样的,所以它可以与"旧"c++一起工作。你必须创建一个类,所有这些开销(代码和类型方面)只是为了在线程中运行。

如果你只是想启动一个线程,我认为c++11 std::thread是不那么冗长/代码你必须写。您可以只使用lambda或函数指针,并可以提供任意多的参数。所以对于简单的线程,我建议使用c++11线程而不是QThreads。

当然,你更喜欢哪一个可以是一个意见问题。


虽然Qt有几个不同的高级线程对象,但c++没有。如果你需要这些而不是基本线,你可能会想看看这些,或者你根本不需要基本线,但那些更适合你。

QThreadPool或简单的QTimer如果你需要等待的东西。这里有一些关于Qt中裸线程的替代方案的阅读。

QConcurrent也更接近c++11 futureasync,例如,它也有可选的线程池可以运行。