Ответ 1
Когда я создаю объект QTimer в Qt 5 и запускаю его с помощью функции start() функция-член, представляет собой отдельный поток, созданный, который отслеживает время и вызывает функцию таймаута() через регулярные интервалы?
Нет; создание отдельного потока было бы дорогостоящим, и это не обязательно, так что не реализовано QTimer.
Здесь, как программа знает, когда происходит таймаут()?
Метод QTimer:: start() может вызывать функцию системного времени (например, gettimeofday() или аналогичный), чтобы узнать (с точностью до несколько миллисекунд), что было вызвано тем, что был вызван start(). Затем он может добавить к этому времени десять миллисекунд (или любое другое значение), и теперь у него есть запись, указывающая, когда ожидается, что сигнал таймаута() будет испускаться следующим образом.
Итак, имея эту информацию, что она делает, чтобы убедиться, что это происходит?
Ключевым фактом, который следует знать, является то, что тайм-аут 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() вернется, а затем будет выполнен код для ответа на это событие, в результате чего будут выполняться соответствующие действия, такие как обновление виджетов или вызванный сигнал таймаута().
Так как же SleepUntilThereIsSomethingToDo() знает, когда пришло время проснуться и вернуться? Это сильно изменится в зависимости от того, на какой ОС вы работаете, поскольку у разных ОС есть разные API-интерфейсы для работы с подобными вещами, но классический способ UNIX-y реализовать такую функцию будет с POSIX выберите() звонок:
int select(int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout);
Обратите внимание, что select() принимает три разных аргумента fd_set, каждый из которых может указывать количество дескрипторов файлов; путем передачи в соответствующие объекты fd_set этим аргументам вы можете вызвать select() для пробуждения момента, когда операции ввода-вывода становятся возможными в любом из нескольких дескрипторов файлов, которые вы хотите отслеживать, чтобы ваша программа могла обрабатывать I/O без задержки. Однако интересная часть для нас - это последний аргумент, который является аргументом тайм-аута. В частности, вы можете передать объект struct timeval
здесь, который говорит select(): "Если после (этого много) микросекунд не произошло событий ввода-вывода, тогда вы должны просто отказаться и вернуться в любом случае".
Это оказывается очень полезным, потому что, используя этот параметр, функция 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 ...]
}
Надеюсь, это имеет смысл, и вы можете видеть, как цикл события использует аргумент timeout select(), чтобы он мог проснуться и испускать сигнал таймаута() в (приблизительно) времени, которое он ранее вычислял при вызове start().
Btw, если приложение имеет одновременно несколько активных QTimer, что не проблема; в этом случае SleepUntilThereIsSomethingToDo() просто нужно перебрать все активные QTimers, чтобы найти ту, которая имеет наименьшую отметку времени следующего таймаута, и использовать только ту минимальную метку времени для вычисления максимального интервала времени, который выбирает() должно быть разрешено спать. Затем после возвращения select() DoTheThingsThatNeedDoingNow() также выполняет итерацию по активным таймерам и испускает сигнал тайм-аута только для тех, чья печать следующего тайма-времени не больше текущего времени. Цикл событий повторяется (как можно быстрее или медленнее, если необходимо), чтобы придать видимость многопоточного поведения, фактически не требуя нескольких потоков.