QThread 与 std::thread

QThread vs std::thread

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

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

首先我想使用 QThread,但是 在我看来,QThread 不允许您做 std::thread 那样多的事情,例如,在阅读时"C++ Concurrency in Action" 作者 Anthony Williams,我看到可以通过 std::thread t1(&Class::function, this, ...); 之类的操作要求 std::thread 从另一个线程执行一个函数,这对于 QThread 来说似乎是不可能的。

我最想拥有的机制是一种表达函数是在当前线程还是在另一个线程中执行的方式。

你会选择哪一个来做那件事,为什么?

QThread 如果你想将线程集成到 Qt 系统中(比如必须发出信号或连接到某些插槽)是很好的

虽然 QThread 的布局仍在进行中,但它可以与 "old" c++ 一起使用。您必须创建一个 class 和所有这些开销(代码和打字明智)只是为了 运行 在一个线程中。

如果您只是想启动一个线程,我认为 c++11 std::thread 较少 verbose/code 您必须编写。您可以只使用 lambda 或函数指针,并可以根据需要提供任意数量的参数。因此,对于简单的线程,我建议在 QThreads 上使用 c++11 线程。

当然,你更喜欢哪一个是见仁见智的。


虽然 Qt 有几个不同的高级线程对象,但 C++ 没有。如果您需要其中一些而不是基本线程,或者您可能根本不需要基本线程但那些更适合您,您可能需要研究这些。

QThreadPool 或简单的 QTimer if you need to wait for things. Here 是关于 Qt 中裸线程的替代方案的一些阅读。

例如,

QConcurrent 也更接近于 c++11 futureasync,并且还有可选的线程池 运行。

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

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

所以你可以将你的功能封装在不同的工作对象中,比如 SafetyCheckerPrinterServoDriverJetDriver 等等,为每个对象创建一个实例,将其移至专用线程即可。您仍然可以调用 "block" 而不是使用细粒度事件的函数,并使用原子或互斥锁来进行线程间同步。这没什么问题,只要你不阻塞 main/gui 线程。

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

也就是说,使用 Qt 的非图形用户界面的东西有其自身的优点,它可以让您更轻松地进行更简洁、更灵活的设计,并且如果您正确实现,您仍然可以获得多线程的好处。您仍然可以使用事件驱动的方法来管理整个事情,这比仅使用 std::thread 要容易得多,后者是一个低得多的级别构造。您可以使用事件驱动的方法来设置、配置、监视和管理设计,而关键部分可以在辅助线程中的阻塞函数中执行,以尽可能低的同步开销实现细粒度控制。

澄清 - 答案并不关注异步任务执行,因为其他两个答案已经关注,而且正如我在评论中提到的那样,异步任务并不是真正用于控制应用程序。它们适用于执行小任务,这些任务仍然需要比您想要阻塞主线程的时间更多的时间。作为推荐的准则,所有需要超过 25 毫秒的事情都最好异步执行。而打印可能需要几分钟甚至几小时,并且意味着连续 运行 控制功能,并行和使用同步。异步任务不会为您提供控制应用程序的性能、延迟和顺序保证。

std::threadQThread 的主要问题是它按照罐子上说的那样做:为您创建一个线程,一个可能只做一件事的线程。 运行 使用 std::thread 的函数 "concurrently" 非常浪费:线程是昂贵的资源,因此只为 运行 某些函数创建一个线程通常是矫枉过正。

虽然 std::thread t1(&Class::function, this, ...) 看起来不错,但通常是过早的悲观情绪,建议将其作为 "doing things concurrently" 的某种通用方式是恕我直言错误的。你可以做得更好。

  1. 如果要在工作线程中同时 运行 一个 function/functor/method,请使用 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"});  });
      }
    };