Goo*_*eds 24 c++ qt multithreading timer qtimer
当我
QTimer在Qt 5中创建一个对象,并使用start()成员函数启动它时,是否创建了一个单独的线程来跟踪时间并timeout()定期调用该函数?
例如,
QTimer *timer = new QTimer;
timer->start(10);
connect(timer,SIGNAL(timeout()),someObject,SLOT(someFunction()));
Run Code Online (Sandbox Code Playgroud)
在这里,程序如何知道何时timeout()发生?我认为它必须在一个单独的线程中运行,因为我没有看到顺序程序如何跟踪时间并继续同时执行.但是,我无法在Qt文档或其他任何地方找到有关此信息的任何信息以确认这一点.
我已经阅读了官方文档,而StackOverflow上的某些问题就像这样,这看起来非常相关,但我无法通过它们得到答案.
任何人都可以解释一个QTimer对象的工作机制吗?
事件由操作系统异步传递,这就是看起来还有其他事情发生的原因.有,但不是在你的程序中.
这是否意味着timeout()由操作系统处理?是否有一些硬件能够跟踪时间并以适当的间隔发送中断?但如果是这种情况,由于许多计时器可以同时独立运行,每个计时器如何被单独跟踪?
机制是什么?
谢谢.
Jer*_*ner 27
当我在Qt 5中创建一个QTimer对象,并使用start()成员函数启动它时,是否创建了一个单独的线程来跟踪时间并定期调用timeout()函数?
没有; 创建一个单独的线程将是昂贵的,并没有必要,所以这不是QTimer的实现方式.
这里,程序如何知道timeout()何时发生?
QTimer :: start()方法可以调用系统时间函数(例如gettimeofday()或类似函数)来查找(在几毫秒内)调用start()的时间.然后,它可以添加10毫秒(或您指定的任何值),现在它有一条记录,指示何时应该发出timeout()信号.
因此,有了这些信息,它会做些什么来确保这一点?
要知道的关键事实是QTimer超时信号发射仅在Qt程序在Qt的事件循环内执行时才有效.几乎每个Qt程序都会有这样的东西,通常在它的main()函数的底部附近:
QApplication app(argc, argv);
[...]
app.exec();
Run Code Online (Sandbox Code Playgroud)
请注意,在典型的应用程序中,几乎所有应用程序的时间都将花在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;
}
Run Code Online (Sandbox Code Playgroud)
因此,当您的应用程序处于空闲状态时,该进程将在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);
Run Code Online (Sandbox Code Playgroud)
请注意,select()采用三个不同的fd_set参数,每个参数都可以指定多个文件描述符; 通过将适当的fd_set对象传递给那些参数,可以使select()在您需要监视的一组文件描述符中的任何一个上唤醒I/O操作,以便程序可以处理I/O没有延迟.然而,对我们来说有趣的部分是最终参数,这是一个超时参数.特别是,您可以在struct timeval这里传入一个说明为select()的对象:"如果在(这么多)微秒之后没有发生I/O事件,那么您应该放弃并返回".
事实证明这非常有用,因为通过使用该参数,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 ...]
}
Run Code Online (Sandbox Code Playgroud)
希望这是有道理的,你可以看到事件循环如何使用select()的超时参数来允许它唤醒并在(大约)调用start时先前计算的时间发出timeout()信号( ).
顺便说一句,如果应用程序同时有多个QTimer激活,那没问题; 在这种情况下,SleepUntilThereIsSomethingToDo()只需迭代所有活动的QTimers以找到具有最小next-time-time戳的那个,并且仅使用该最小时间戳来计算select()的最大时间间隔应该被允许睡觉.然后在select()返回后,DoTheThingsThatNeedDoingNow()也会迭代活动计时器,并仅为那些下一个超时时间戳不大于当前时间的人发出超时信号.事件循环重复(根据需要快速或缓慢)以提供多线程行为的外观而不需要实际需要多个线程.
纵观约计时器文件,并在源代码QTimer和QObject我们可以看到,计时器被分配给该对象的线程/事件循环运行。从文档:
为了
QTimer工作,您的应用程序中必须有一个事件循环;也就是说,你必须在QCoreApplication::exec()某个地方打电话。定时器事件只会在事件循环运行时传递。在多线程应用程序中,您可以
QTimer在任何具有事件循环的线程中使用。要从非 GUI 线程启动事件循环,请使用QThread::exec(). Qt 使用计时器的线程关联来确定哪个线程将发出timeout()信号。因此,您必须在其线程中启动和停止计时器;不可能从另一个线程启动计时器。
在内部,QTimer只是使用该QObject::startTimer方法在一定时间后触发。这个本身以某种方式告诉线程它正在运行一段时间后触发。
因此,只要您不阻塞事件队列,您的程序就可以连续运行并跟踪计时器。如果您担心您的计时器不是 100% 准确,请尝试将长时间运行的回调移出它们自己线程中的事件队列,或者为计时器使用不同的事件队列。
小智 5
QTimer 对象将自身注册到 EventDispatcher (QAbstractEventDispatcher) 中,它会在每次特定注册 QTimer 超时时发送 QTimerEvent 类型的事件。例如,在 GNU/Linux 上,有一个名为 QEventDispatcherUNIXPrivate 的 QAbstractEventDispatcher 私有实现,它在计算时考虑了平台 api。QTimerEvent 从 QEventDispatcherUNIXPrivate 发送到 QTimer 对象所属的同一线程的事件循环的队列中,即被创建。
QEventDispatcherUNIXPrivate 不会因为某些操作系统事件或时钟而触发 QTimerEvent,而是因为它会在 QTimer 所在的线程事件循环调用 processEvents 时定期检查超时。在这里:https : //code.woboq.org/qt5/qtbase/src/corelib/kernel/qeventdispatcher_unix.cpp.html#_ZN27QEventDispatcherUNIXPrivateC1Ev