基本的なイベントループをどのように実装しますか?
-
20-08-2019 - |
質問
GUIツールキットを使用したことがある場合は、すべてが完了した後に実行する必要があるイベントループ/メインループがあり、さまざまなイベントに対応してアプリケーションを実行し続けることができます。たとえば、Qtの場合、main()でこれを行います。
int main() {
QApplication app(argc, argv);
// init code
return app.exec();
}
この場合、app.exec()はアプリケーションのメインループです。
この種のループを実装する明白な方法は次のとおりです。
void exec() {
while (1) {
process_events(); // create a thread for each new event (possibly?)
}
}
しかし、これはCPUを100%に制限し、実用的ではありません。さて、CPUを完全に消費することなく応答するようなイベントループをどのように実装できますか?
回答はPythonおよび/またはC ++で高く評価されています。ありがとう。
脚注:学習のために、独自の信号/スロットを実装し、それらを使用してカスタムイベントを生成します(例:go_forward_event(steps)
)。ただし、システムイベントを手動で使用する方法を知っている場合は、それについてもお知らせします。
解決
以前は同じことをよく考えていました!
GUIのメインループは、擬似コードでは次のようになります。
void App::exec() {
for(;;) {
vector<Waitable> waitables;
waitables.push_back(m_networkSocket);
waitables.push_back(m_xConnection);
waitables.push_back(m_globalTimer);
Waitable* whatHappened = System::waitOnAll(waitables);
switch(whatHappened) {
case &m_networkSocket: readAndDispatchNetworkEvent(); break;
case &m_xConnection: readAndDispatchGuiEvent(); break;
case &m_globalTimer: readAndDispatchTimerEvent(); break;
}
}
}
<!> quot; Waitable <!> quot;とは何ですか?まあ、それはシステムに依存しています。 UNIXでは、<!> quot; file descriptor <!> quot;と呼ばれます。および<!> quot; waitOnAll <!> quot; :: selectシステムコールです。いわゆるvector<Waitable>
はUNIXでは::fd_set
であり、<!> quot; whatHappened <!> quot; FD_ISSET
を介して実際にクエリされます。実際の待機可能ハンドルはさまざまな方法で取得されます。たとえば、m_xConnection
は:: XConnectionNumber()から取得できます。 X11は、このための高レベルでポータブルなAPIも提供します-:: XNextEvent()-しかし、それを使用する場合、いくつかのイベントソースを同時に待機することはできません。
ブロッキングはどのように機能しますか? <!> quot; waitOnAll <!> quot; <!> quot; sleep list <!> quot;にプロセスを置くようにOSに指示するsyscallです。これは、いずれかのwaitableでイベントが発生するまでCPU時間を与えられないことを意味します。これは、プロセスがアイドル状態であり、CPUを0%消費していることを意味します。イベントが発生すると、プロセスは短時間反応してからアイドル状態に戻ります。 GUIアプリは、ほとんどの時間をアイドリングに費やします。
睡眠中のすべてのCPUサイクルはどうなりますか?依存します。別のプロセスがそれらを使用する場合があります。そうでない場合、OSはCPUをビジーループするか、一時的な低電力モードなどになります。
詳細についてはお問い合わせください!
他のヒント
Python:
ツイストリアクターの実装を見ることができます。これはおそらく、Pythonのイベントループの最適な実装です。 Twistedのリアクターはインターフェースの実装であり、実行するリアクターのタイプを指定できます:select、epoll、kqueue(すべてこれらのシステムコールを使用するac apiに基づく)、QTおよびGTKツールキットに基づくリアクターもあります。
単純な実装では、selectを使用します。
#echo server that accepts multiple client connections without forking threads
import select
import socket
import sys
host = ''
port = 50000
backlog = 5
size = 1024
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((host,port))
server.listen(backlog)
input = [server,sys.stdin]
running = 1
#the eventloop running
while running:
inputready,outputready,exceptready = select.select(input,[],[])
for s in inputready:
if s == server:
# handle the server socket
client, address = server.accept()
input.append(client)
elif s == sys.stdin:
# handle standard input
junk = sys.stdin.readline()
running = 0
else:
# handle all other sockets
data = s.recv(size)
if data:
s.send(data)
else:
s.close()
input.remove(s)
server.close()
一般的には、セマフォのカウントを使用してこれを行います。
>- セマフォはゼロから始まります。
- セマフォでイベントループが待機します。
- イベントが入ると、セマフォがインクリメントされます。
- イベントハンドラは、セマフォのブロックを解除してデクリメントし、イベントを処理します。
- すべてのイベントが処理されると、セマフォはゼロになり、イベントループは再びブロックされます。
それほど複雑にしたくない場合は、ごく短いスリープ時間でwhileループにsleep()呼び出しを追加するだけで済みます。これにより、メッセージ処理スレッドはCPU時間を他のスレッドに譲ります。 CPUはもう100%に固定されませんが、それでもかなり無駄です。
ZeroMQは、イベント駆動型のコードを大幅に簡素化し、パフォーマンスの面でも最も効率的なソリューションです。 ZeroMQを使用したスレッド間の通信は、セマフォまたはローカルUNIXソケットを使用するよりも(速度の点で)はるかに高速です。 ZeroMQは100%ポータブルなソリューションでもありますが、他のすべてのソリューションはコードを特定のオペレーティングシステムに結び付けます。