QThread::msleep 在 MacOS 上休眠错误时间

QThread::msleep on MacOS sleep a error time

我想用qthread做一个ping job并在指定的时间休眠,但有时线程休眠时间是错误的,我在Qt5.6.3和5.9.3上都试过了,都不行。

这是我的演示代码:

#include "mainwindow.h"
#include <QApplication>
#include <QThread>
#include <QDebug>
#include <QDateTime>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    QThread *thread = new QThread;
    QObject::connect(thread,&QThread::started,[=]{
        while(true){
            QDateTime dateTime = QDateTime::currentDateTime();
            qDebug() << dateTime.toString("yyyy-MM-dd hh:mm:ss.zzz") << "start..." ;
            QThread::msleep(7000);
        }
    });
    thread->start();

    return a.exec();
}

但是但是但是控制台输出是:

"2018-06-01 17:40:22.603" start...
"2018-06-01 17:40:29.608" start...
"2018-06-01 17:40:36.612" start...
"2018-06-01 17:40:43.613" start...
"2018-06-01 17:40:50.618" start...
"2018-06-01 17:40:57.623" start...
"2018-06-01 17:41:04.628" start...
"2018-06-01 17:41:11.629" start...
"2018-06-01 17:41:18.633" start...
"2018-06-01 17:41:25.634" start...
"2018-06-01 17:41:32.639" start...
"2018-06-01 17:41:39.640" start...
"2018-06-01 17:41:46.645" start...
"2018-06-01 17:41:53.650" start...
"2018-06-01 17:42:00.655" start...
"2018-06-01 17:42:07.659" start...
"2018-06-01 17:42:14.661" start...
"2018-06-01 17:42:21.666" start...
"2018-06-01 17:42:28.670" start...
"2018-06-01 17:42:35.674" start...
"2018-06-01 17:42:42.674" start...
"2018-06-01 17:42:59.673" start...

"2018-06-01 17:43:16.398" start...
"2018-06-01 17:43:23.399" start...
"2018-06-01 17:43:40.399" start...
"2018-06-01 17:43:50.297" start...
"2018-06-01 17:43:57.297" start...
"2018-06-01 17:44:14.297" start...
"2018-06-01 17:44:31.297" start...
"2018-06-01 17:44:48.296" start...
"2018-06-01 17:45:05.296" start...
"2018-06-01 17:45:22.296" start...
"2018-06-01 17:45:31.299" start...
"2018-06-01 17:45:48.299" start...
"2018-06-01 17:46:04.806" start...

我标记了错误行,有人知道为什么吗???? 谢谢

QThread 和 msleep 本身都没有问题。它们根本不是为这项任务而设计的。

macOS 不是实时操作系统,因此一旦您将线程置于休眠状态,绝对不能保证它会在精确的时间后唤醒。它会尽力而为,但无论出于何种原因,它可能需要更长/更短的时间。

当您需要精确计时时,您可以使用 QTimerQt::PreciseTimer(文档 here)。

举个例子:

QTimer *timer = new QTimer(this);
timer->setTimerType(Qt::PreciseTimer)
timer->setInterval(7000);
connect(timer, &QTimer::timeout(), this, []{
   QDateTime dateTime = QDateTime::currentDateTime();
   qDebug() << dateTime.toString("yyyy-MM-dd hh:mm:ss.zzz") << "start..." ;
});
timer->start();

这完全在您的方法的容差范围内。您不是 运行 某种实时控制系统。您 运行 它在桌面平台上。这样的计时精度是意料之中的。

但你当然也做错了,因为这样的睡眠总是会积累正的时间漂移​​。随着您在 sleep 调用之间做更多的工作,漂移会变得更糟:sleep 假设您从未睡过头,并且 I/O 代码花费零时间。这当然是一个错误的假设。系统计时器对此进行补偿。

至少,确保您的睡眠可以弥补任何失误 (mode = CompensatedSleep),或者最好使用计时器 (mode = Timer)。下面的示例说明了所有三种方法,包括您原来的方法 (mode = DriftySleep)。

// https://github.com/KubaO/Whosebugn/tree/master/questions/timer-modes-50640879
#include <QtWidgets>

struct Thread final : QThread {
   ~Thread() override { finish(); wait(); }
   void finish() { quit(); requestInterruption(); }
};

int main(int argc, char *argv[]) {
   QApplication app(argc, argv);
   QPushButton toggle{"Click to Stop"};
   toggle.setMinimumSize(300, 150);
   toggle.show();

   Thread thread;
   QTimer timer;
   QObject::connect(&toggle, &QPushButton::clicked, [&]{
      thread.finish();
      toggle.setDisabled(true);
   });
   QObject::connect(&thread, &Thread::finished, &toggle, &QWidget::close);

   constexpr enum { DriftySleep, CompensatedSleep, Timer } mode = CompensatedSleep;
   qint64 constexpr setPeriod = 7000;

   auto dump = [period = Q_INT64_C(0), watch = QElapsedTimer()](qint64 load = {}) mutable {
      qint64 current = watch.isValid() ? watch.elapsed() : 0;
      auto dateTime = QDateTime::currentDateTime();
      qDebug() << dateTime.toString("yyyy-MM-dd hh:mm:ss.zzz") << current;
      if (!watch.isValid()) {
         watch.start();
         period = load;
      } else
         period = watch.restart();
      return period;
   };
   if (mode != Timer) QObject::connect(&thread, &Thread::started, [&]{
      auto period = dump(setPeriod);
      while (!thread.isInterruptionRequested()) {
         QThread::msleep(setPeriod*2 - ((mode == CompensatedSleep) ? period : setPeriod));
         period = dump();
      }
   });
   else {
      timer.setTimerType(Qt::PreciseTimer);
      timer.start(setPeriod);
      timer.moveToThread(&thread);
      QObject::connect(&thread, &Thread::finished, [&]{ timer.moveToThread(thread.thread()); });
      QObject::connect(&thread, &Thread::started, [&]{ dump(setPeriod); });
      QObject::connect(&timer, &QTimer::timeout, [&]{ dump(); });
   }
   thread.start();
   return app.exec();
}