c++ - temporizador - ¿Cómo implementarías un ciclo de eventos básico?
nodejs clock (4)
Si ha trabajado con gui toolkits, sabe que hay un bucle de eventos / bucle principal que debe ejecutarse después de que se haga todo, y que mantendrá la aplicación activa y en respuesta a diferentes eventos. Por ejemplo, para Qt, haría esto en main ():
int main() {
QApplication app(argc, argv);
// init code
return app.exec();
}
Que en este caso, app.exec () es el bucle principal de la aplicación.
La forma obvia de implementar este tipo de bucle sería:
void exec() {
while (1) {
process_events(); // create a thread for each new event (possibly?)
}
}
Pero esto limita la CPU al 100% y es prácticamente inútil. Ahora, ¿cómo puedo implementar un bucle de eventos de este tipo que responda sin consumir la CPU por completo?
Las respuestas son apreciadas en Python y / o C ++. Gracias.
Nota al pie: Para aprender, implementaré mis propias señales / ranuras, y las usaría para generar eventos personalizados (por ejemplo, go_forward_event(steps)
). Pero si sabe cómo puedo usar los eventos del sistema manualmente, también me gustaría saberlo.
Generalmente haría esto con algún tipo de semáforo de conteo :
- El semáforo comienza en cero.
- El bucle de eventos espera en el semáforo.
- El evento (s) entra, el semáforo se incrementa.
- El controlador de eventos desbloquea y disminuye el semáforo y procesa el evento.
- Cuando se procesan todos los eventos, el semáforo es cero y los bucles de evento se bloquean nuevamente.
Si no quieres complicarte tanto, puedes agregar una llamada sleep () en tu bucle while con un tiempo de inactividad trivialmente pequeño. Eso hará que el subproceso de procesamiento de mensajes ceda el tiempo de CPU a otros subprocesos. La CPU no se fijará al 100% más, pero sigue siendo un desperdicio.
Pitón:
Puede ver la implementación del reactor retorcido, que es probablemente la mejor implementación para un bucle de eventos en Python. Los reactores en Twisted son implementaciones de una interfaz y puede especificar un tipo de reactor para ejecutar: select, epoll, kqueue (todos basados en ac api que usan esas llamadas al sistema), también hay reactores basados en los kits de herramientas QT y GTK.
Una implementación simple sería utilizar 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()
Solía preguntarme mucho sobre lo mismo!
Un bucle principal de GUI tiene este aspecto, en pseudo-código:
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é es un "Waitable"? Bueno, es dependiente del sistema. En UNIX se llama "descriptor de archivo" y "waitOnAll" es la llamada al sistema de selección. El llamado vector<Waitable>
es un ::fd_set
en UNIX, y "whatHappened" se consulta en realidad a través de FD_ISSET
. Los controles waitable reales se adquieren de varias formas, por ejemplo, m_xConnection
puede tomarse de :: XConnectionNumber (). X11 también proporciona una API portátil de alto nivel para esto - :: XNextEvent () - pero si lo utilizara, no podría esperar en varios orígenes de eventos simultáneamente .
¿Cómo funciona el bloqueo? "waitOnAll" es un syscall que le dice al sistema operativo que ponga su proceso en una "lista de espera". Esto significa que no se le da tiempo de CPU hasta que ocurra un evento en uno de los waitables. Esto, entonces, significa que su proceso está inactivo, consumiendo 0% de CPU. Cuando ocurre un evento, su proceso reaccionará brevemente y luego volverá al estado inactivo. Las aplicaciones GUI pasan casi todo el tiempo inactivas.
¿Qué pasa con todos los ciclos de CPU mientras estás durmiendo? Depende. A veces, otro proceso tendrá un uso para ellos. Si no es así, su sistema operativo hará un ciclo de ocupado de la CPU o lo pondrá en modo de bajo consumo temporal, etc.
¡Por favor pregunte por mas detalles!
Usaría una biblioteca de mensajes simple y ligera llamada ZeroMQ ( http://www.zeromq.org/ ). Es una biblioteca de código abierto (LGPL). Esta es una biblioteca muy pequeña; En mi servidor, todo el proyecto se compila en unos 60 segundos.
ZeroMQ simplificará enormemente su código impulsado por eventos, Y también es LA solución más eficiente en términos de rendimiento. La comunicación entre hilos que utilizan ZeroMQ es mucho más rápida (en términos de velocidad) que el uso de semáforos o sockets locales de UNIX. ZeroMQ también puede ser una solución 100% portátil, mientras que todas las demás soluciones atarían su código a un sistema operativo específico.