Si vous avez travaillé avec les kits d'outils gui, vous savez qu'il existe une boucle d'événement/boucle principale qui doit être exécutée une fois que tout est fait, et qui maintiendra l'application en vie et sensible aux différents événements. Par exemple, pour Qt, vous feriez ceci dans main ():
int main() {
QApplication app(argc, argv);
// init code
return app.exec();
}
Dans ce cas, app.exec () est la boucle principale de l'application.
La manière évidente de mettre en œuvre ce type de boucle serait:
void exec() {
while (1) {
process_events(); // create a thread for each new event (possibly?)
}
}
Mais cela limite le CPU à 100% et est pratiquement inutile. Maintenant, comment puis-je implémenter une telle boucle d'événements qui est réactive sans manger complètement le CPU?
Les réponses sont appréciées en Python et/ou C++. Merci.
Note de bas de page: Par souci d'apprentissage, j'implémenterai mes propres signaux/emplacements, et je les utiliserais pour générer des événements personnalisés (par exemple go_forward_event(steps)
). Mais si vous savez comment utiliser manuellement les événements système, j'aimerais aussi en savoir plus.
Je me demandais beaucoup la même chose!
Une boucle principale GUI ressemble à ceci, en pseudo-code:
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;
}
}
}
Qu'est-ce qu'un "Waitable"? Eh bien, cela dépend du système. Sous UNIX, il est appelé "descripteur de fichier" et "waitOnAll" est l'appel système :: select. La dite vector<Waitable>
est un ::fd_set
sous UNIX, et "whatHappened" est en fait interrogé via FD_ISSET
. Les poignées attendues réelles sont acquises de différentes manières, par exemple m_xConnection
peut être extrait de :: XConnectionNumber (). X11 fournit également une API portable de haut niveau pour cela - :: XNextEvent () - mais si vous deviez l'utiliser, vous ne seriez pas en mesure d'attendre plusieurs sources d'événements simultanément.
Comment fonctionne le blocage? "waitOnAll" est un appel système qui indique au système d'exploitation de mettre votre processus sur une "liste de veille". Cela signifie que vous ne disposez d'aucun temps CPU jusqu'à ce qu'un événement se produise sur l'un des serveurs. Cela signifie donc que votre processus est inactif et consomme 0% de CPU. Lorsqu'un événement se produit, votre processus y réagit brièvement, puis revient à l'état inactif. Les applications GUI passent presque toutes leur temps au ralenti.
Qu'advient-il de tous les cycles du processeur pendant que vous dormez? Dépend. Parfois, un autre processus leur sera utile. Sinon, votre système d'exploitation va boucler le processeur en boucle occupée, ou le mettre en mode temporaire de faible consommation, etc.
S'il vous plaît demander des détails supplémentaires!
Python:
Vous pouvez regarder l'implémentation du Twisted réacteur qui est probablement la meilleure implémentation pour une boucle d'événement en python. Les réacteurs dans Twisted sont des implémentations d'une interface et vous pouvez spécifier un type de réacteur à exécuter: select, epoll, kqueue (tous basés sur un api c utilisant ces appels système), il existe également des réacteurs basés sur les kits d'outils QT et GTK.
Une implémentation simple serait d'utiliser 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()
En général, je le ferais avec une sorte de comptage du sémaphore :
Si vous ne voulez pas que cela soit compliqué, vous pouvez simplement ajouter un appel sleep () dans votre boucle while avec un temps de sommeil trivialement petit. Cela entraînera votre thread de traitement des messages à céder son temps CPU à d'autres threads. Le CPU ne sera plus indexé à 100%, mais c'est quand même un gaspillage.
J'utiliserais une bibliothèque de messagerie simple et légère appelée ZeroMQ ( http://www.zeromq.org/ ). Il s'agit d'une bibliothèque open source (LGPL). Ceci est une très petite bibliothèque; sur mon serveur, l'ensemble du projet se compile en 60 secondes environ.
ZeroMQ simplifiera énormément votre code événementiel, ET c'est aussi LA solution la plus efficace en termes de performances. La communication entre les threads à l'aide de ZeroMQ est beaucoup plus rapide (en termes de vitesse) que l'utilisation de sémaphores ou de sockets UNIX locaux. ZeroMQ serait également une solution 100% portable, tandis que toutes les autres solutions lieraient votre code à un système d'exploitation spécifique.