QTimer对象是否在单独的线程中运行?它的机制是什么?

时间:2017-02-16 16:20:58

标签: c++ multithreading qt timer qtimer

  

当我在Qt 5中创建QTimer对象并使用start()成员函数启动它时,创建了一个单独的线程来跟踪时间并调用timeout()函数定期?

例如,

QTimer *timer = new QTimer;
timer->start(10);
connect(timer,SIGNAL(timeout()),someObject,SLOT(someFunction()));

此处,程序如何知道timeout()何时发生?我认为它必须在一个单独的线程中运行,因为我不知道顺序程序如何跟踪时间并继续同时执行。但是,我无法在Qt文档或其他任何地方找到任何相关信息来确认这一点。

我已阅读the official documentation,而且有关StackOverflow的某些问题,例如thisthis似乎非常相关,但我无法通过它们得到答案。

有人能解释QTimer对象的工作机制吗?

在进一步搜索时,我发现this answer按照Bill提到

  

事件由操作系统异步传递,这就是为什么看起来还有其他事情发生的原因。有,但不在您的计划中。

这是否意味着操作系统处理timeout()?是否有一些硬件能够跟踪时间并以适当的间隔发送中断?但如果是这种情况,由于许多计时器可以同时独立运行,每个计时器如何被单独跟踪?

机制是什么?

谢谢。

3 个答案:

答案 0 :(得分:23)

  

当我在Qt 5中创建QTimer对象时,使用start()启动它   成员函数,是一个创建的单独线程,用于跟踪   time并定期调用timeout()函数?

没有;创建一个单独的线程将是昂贵的,并没有必要,所以这不是QTimer的实现方式。

  

这里,程序如何知道timeout()何时发生?

QTimer :: start()方法可以调用系统时间函数(例如gettimeofday()或类似函数)来查找(在几毫秒内)调用start()的时间。然后,它可以添加10毫秒(或您指定的任何值),现在它有一条记录,指示何时应该发出timeout()信号。

因此,有了这些信息,它会做些什么来确保这一点?

要知道的关键事实是QTimer超时信号发射仅在Qt程序在Qt的事件循环内执行时才有效。几乎每个Qt程序都会有这样的东西,通常在它的main()函数的底部附近:

QApplication app(argc, argv);
[...]
app.exec();

请注意,在典型的应用程序中,几乎所有应用程序的时间都将花在exec()调用中;也就是说,在应用程序退出之前,app.exec()调用将不会返回

那么在程序运行时,exec()调用内部会发生什么?有了像Qt这样的大型复杂库,它必然会变得复杂,但是说它正在运行一个概念性的事件循环并不是太简单:

 while(1)
 {
     SleepUntilThereIsSomethingToDo();  // not a real function name!
     DoTheThingsThatNeedDoingNow();     // this is also a name I made up
     if (timeToQuit) break;
 }

因此,当您的应用程序处于空闲状态时,该进程将在SleepUntilThereIsSomethingToDo()调用内进入休眠状态,但只要事件到达需要处理(例如用户移动鼠标,或按键,或数据到达)在套接字或其它方面),SleepUntilThereIsSomethingToDo()将返回,然后将执行响应该事件的代码,从而产生适当的操作,例如小部件更新或调用timeout()信号。

那么SleepUntilThereIsSomethingToDo()如何知道何时醒来并返回?这将根据您运行的操作系统而有很大差异,因为不同的操作系统具有不同的API来处理此类事情,但实现此类功能的经典UNIX-y方式将是POSIX select()调用:

int select(int nfds, 
           fd_set *readfds, 
           fd_set *writefds,
           fd_set *exceptfds, 
           struct timeval *timeout);

请注意,select()接受三个不同的fd_set参数,每个参数都可以指定多个文件描述符;通过将适当的fd_set对象传递给那些参数,可以使select()在您需要监视的一组文件描述符中的任何一个上唤醒I / O操作,以便程序可以处理I / O没有延迟。然而,对我们来说有趣的部分是最终参数,这是一个超时参数。特别是,你可以传入一个struct timeval对象来说明select():“如果在这么多微秒之后没有发生I / O事件,那么你应该放弃并返回”。< / p>

结果证明非常有用,因为通过使用该参数,SleepUntilThereIsSomethingToDo()函数可以执行类似这样的操作(伪代码):

void SleepUntilThereIsSomethingToDo()
{
   struct timeval now = gettimeofday();  // get the current time
   struct timeval nextQTimerTime = [...];  // time at which we want to emit a timeout() signal, as was calculated earlier inside QTimer::start()
   struct timeval maxSleepTimeInterval = (nextQTimerTime-now);
   select([...], &maxSleepTimeInterval);  // sleep until the appointed time (or until I/O arrives, whichever comes first)
}

void DoTheThingsThatNeedDoingNow()
{
   // Is it time to emit the timeout() signal yet?
   struct timeval now = gettimeofday();
   if (now >= nextQTimerTime) emit timeout();

   [... do any other stuff that might need doing as well ...]
}   

希望这是有道理的,你可以看到事件循环如何使用select()的超时参数来允许它唤醒并在(大约)你之前计算的时间发出timeout()信号叫做start()。

如果应用程序同时激活了多个QTimer,那就没问题了;在这种情况下,SleepUntilThereIsSomethingToDo()只需迭代所有活动的QTimers以找到具有最小next-time-time戳的那个,并且仅使用该最小时间戳来计算select()的最大时间间隔应该被允许睡觉。然后在select()返回后,DoTheThingsThatNeedDoingNow()也会迭代活动计时器,并仅为那些下一个超时时间戳不大于当前时间的人发出超时信号。事件循环重复(根据需要快速或缓慢)以提供多线程行为的外观而不需要实际需要多个线程。

答案 1 :(得分:4)

查看documentation about timerssource code of QTimer and QObject,我们可以看到计时器正在分配给对象的线程/事件循环中运行。来自doc:

  

要使main queue起作用,您的应用程序中必须有一个事件循环;也就是说,你必须在某处调用QTimer。定时器事件仅在事件循环运行时传递。

     

在多线程应用程序中,您可以在具有事件循环的任何线程中使用QCoreApplication::exec()。要从非GUI线程启动事件循环,请使用QTimer。 Qt使用计时器的线程亲和性来确定哪个线程将发出QThread::exec()信号。因此,您必须在其线程中启动和停止计时器;无法从另一个线程启动计时器。

在内部,timeout()只需使用QTimer方法在一定时间后开火。这个本身以某种方式告诉它运行的线程在经过一段时间后才会触发。

因此,只要您不阻止事件队列,您的程序就可以持续良好地运行并跟踪计时器。如果您担心计时器不是100%准确,请尝试将长时间运行的回调移出其自己的线程中的事件队列,或者为计时器使用不同的事件队列。

答案 2 :(得分:2)

QTimer对象将其自身注册到EventDispatcher(QAbstractEventDispatcher)中,该事件将在每次特定的已注册QTimer超时时负责发送QTimerEvent类型的事件。例如,在GNU / Linux上,有一个名为QEventDispatcherUNIXPrivate的QAbstractEventDispatcher的私有实现,该实现在考虑当前平台api的情况下进行计算。 QTimerEvent从QEventDispatcherUNIXPrivate发送到QTimer对象所属的同一线程(即已创建)的事件循环的队列中。

QEventDispatcherUNIXPrivate不会由于某个OS系统事件或时钟而触发QTimerEvent,而是因为当QTimer也驻留在其中的线程事件循环调用processEvents时,它会定期检查超时。 Se在这里:https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qeventdispatcher_unix.cpp.html#_ZN27QEventDispatcherUNIXPrivateC1Ev