web-dev-qa-db-ja.com

QTimerオブジェクトは別のスレッドで実行されますか?そのメカニズムは何ですか?

Qt 5でQTimerオブジェクトを作成し、start()メンバー関数を使用して開始すると、時間を追跡してtimeout()関数を呼び出す別のスレッドが作成されます定期的に?

例えば、

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

ここで、プログラムはtimeout()がいつ発生したかをどのように知るのでしょうか?順次プログラムが時間を追跡し、同時に実行を継続する方法がわからないため、別のスレッドで実行する必要があると思います。ただし、Qtのドキュメントまたはこれを確認する他の場所で、これに関する情報を見つけることができませんでした。

私は 公式ドキュメント を読み、 thisthis などのStackOverflowの特定の質問は非常に関連しているようですが、答えを得ることができませんでしたそれら。

QTimerオブジェクトが機能するメカニズムを説明できる人はいますか?

さらに検索すると、 this answer by Bill によると、

イベントはOSによって非同期に配信されるため、他に何かが発生しているように見えます。ありますが、プログラムにはありません。

timeout()がOSによって処理されるということですか?時間を追跡し、適切な間隔で割り込みを送信するハードウェアはありますか?しかし、この場合、多くのタイマーを同時に独立して実行できるため、各タイマーを個別に追跡するにはどうすればよいですか?

メカニズムは何ですか?

ありがとうございました。

24
GoodDeeds

Qt 5でQTimerオブジェクトを作成し、start()メンバー関数を使用して開始すると、時間を追跡し、定期的にtimeout()関数を呼び出す別のスレッドが作成されますか?

いいえ。別のスレッドを作成するのは高価であり、必要ではないため、QTimerの実装方法はそうではありません。

ここで、プログラムはtimeout()がいつ発生するかをどのように知るのですか?

QTimer :: start()メソッドは、システム時間関数(たとえば gettimeofday() など)を呼び出して、start()が呼び出された時刻を(数ミリ秒以内に)見つけることができます。次に、その時間に10ミリ秒(または指定した値)を追加し、次に、timeout()シグナルが次に発行されることになっていることを示すレコードを持ちます。

それで、その情報を持っていて、それが起こることを確かめるために、それは何をしますか?

知っておくべき重要な事実は、QTimer timeout-signal-emissionは、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()は、目覚めて戻ってくる時間をどのように知るのでしょうか?これは、実行しているOSによって大きく異なります。異なるOSにはこの種の処理のための異なるAPIがありますが、このような関数を実装する古典的なUNIX-yの方法はPOSIX select() 呼び出し:

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

Select()は3つの異なる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 ...]
}   

うまくいけば、それが理にかなっており、イベントループがselect()のタイムアウト引数を使用して、(ほぼ)start( )。

ところで、アプリが同時に複数のQTimerをアクティブにしている場合、それは問題ありません。その場合、SleepUntilThereIsSomethingToDo()は、すべてのアクティブなQTimersを反復処理して、次のタイムアウトタイムスタンプが最小のものを見つけ、select()の最大時間間隔の計算にその最小タイムスタンプのみを使用する必要があります。睡眠を許可する必要があります。その後、select()が戻った後、DoTheThingsThatNeedDoingNow()はアクティブタイマーを繰り返し処理し、next-timeout-timeスタンプが現在の時間より大きくないタイマーに対してのみタイムアウト信号を発信します。イベントループが繰り返され(必要に応じて高速または低速で)、実際に複数のスレッドを必要とせずに、マルチスレッド動作の外観を提供します。

26
Jeremy Friesner

タイマーに関するドキュメントQTimerおよびQObjectのソースコード を見ると、タイマーがスレッドで実行されていることがわかります。オブジェクトに割り当てられた/ eventループ。ドキュメントから:

QTimerが機能するには、アプリケーションにイベントループが必要です。つまり、QCoreApplication::exec()をどこかで呼び出す必要があります。タイマーイベントは、イベントループの実行中にのみ配信されます。

マルチスレッドアプリケーションでは、イベントループのある任意のスレッドでQTimerを使用できます。非GUIスレッドからイベントループを開始するには、QThread::exec()を使用します。 Qtはタイマーのスレッドアフィニティを使用して、どのスレッドがthetimeout()シグナルを発行するかを決定します。このため、スレッド内でタイマーを開始および停止する必要があります。別のスレッドからタイマーを開始することはできません。

内部的に、QTimerは単にQObject::startTimer一定時間後に起動するメソッド。これ自体が何らかの方法で、一定時間後に起動するスレッドに伝えます。

したがって、イベントキューをブロックしない限り、プログラムは継続的に正常に実行され、タイマーを追跡します。タイマーが100%正確ではないことが心配な場合は、長時間実行されるコールバックを独自のスレッドのイベントキューから移動するか、タイマーに別のイベントキューを使用してください。

4
msrd0

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

2
Iurie Nistor