Domanda

Se hai lavorato con i toolkit gui, sai che esiste un loop di eventi / main-loop che dovrebbe essere eseguito dopo che tutto è stato fatto e che manterrà l'applicazione viva e reattiva a diversi eventi. Ad esempio, per Qt, dovresti farlo in main ():

int main() {
    QApplication app(argc, argv);
    // init code
    return app.exec();
}

Che in questo caso, app.exec () è il ciclo principale dell'applicazione.

Il modo ovvio per implementare questo tipo di loop sarebbe:

void exec() {
    while (1) {
        process_events(); // create a thread for each new event (possibly?)
    }
}

Ma questo limita la CPU al 100% ed è praticamente inutile. Ora, come posso implementare un loop di eventi così reattivo senza consumare del tutto la CPU?

Le risposte sono apprezzate in Python e / o C ++. Grazie.

Nota a piè di pagina: per motivi di apprendimento, implementerò i miei segnali / slot e li userò per generare eventi personalizzati (ad es. go_forward_event(steps)). Ma se sai come posso usare manualmente gli eventi di sistema, vorrei saperlo anche io.

È stato utile?

Soluzione

Mi chiedevo molto della stessa cosa!

Un ciclo principale della GUI è simile al seguente, in pseudo-codice:

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;
        }
    }
}

Che cos'è un " Waitable " ;? Bene, dipende dal sistema. Su UNIX si chiama & Quot; descrittore di file & Quot; e " waitOnAll " è la chiamata di sistema :: select. Il cosiddetto vector<Waitable> è un ::fd_set su UNIX e & Quot; whatHappened & Quot; viene effettivamente interrogato tramite FD_ISSET. Gli handle di waitable effettivi vengono acquisiti in vari modi, ad esempio m_xConnection può essere preso da :: XConnectionNumber (). X11 fornisce anche un'API portatile di alto livello per questo - :: XNextEvent () - ma se dovessi usarlo, non saresti in grado di attendere diverse fonti di eventi contemporaneamente .

Come funziona il blocco? quot &; & waitOnAll quot; è una syscall che dice al sistema operativo di mettere il processo su un " elenco di sospensione " ;. Ciò significa che non viene concesso alcun tempo alla CPU fino a quando non si verifica un evento su uno dei camerieri. Questo, quindi, significa che il processo è inattivo, consumando lo 0% di CPU. Quando si verifica un evento, il processo reagirà brevemente ad esso e quindi tornerà allo stato inattivo. Le app della GUI trascorrono quasi tutte il loro tempo al minimo.

Cosa succede a tutti i cicli della CPU mentre dormi? Dipende. A volte un altro processo può essere utile per loro. In caso contrario, il sistema operativo eseguirà il loop occupato della CPU o la metterà in modalità temporanea a basso consumo, ecc.

Si prega di chiedere ulteriori dettagli!

Altri suggerimenti

Python:

Puoi vedere l'implementazione del Reattore contorto che è probabilmente la migliore implementazione per un loop di eventi in Python. I reattori in Twisted sono implementazioni di un'interfaccia ed è possibile specificare un tipo di reattore da eseguire: select, epoll, kqueue (tutti basati su ac api utilizzando quelle chiamate di sistema), ci sono anche reattori basati sui toolkit QT e GTK.

Una semplice implementazione sarebbe usare 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() 

Generalmente lo farei con una sorta di conteggio del semaforo :

  1. Il semaforo inizia da zero.
  2. Il ciclo di eventi attende sul semaforo.
  3. Evento (i) in arrivo, il semaforo viene incrementato.
  4. Il gestore eventi sblocca e decrementa il semaforo ed elabora l'evento.
  5. Quando tutti gli eventi vengono elaborati, il semaforo è zero e il loop degli eventi si blocca nuovamente.

Se non vuoi complicarti, puoi semplicemente aggiungere una chiamata sleep () nel tuo ciclo while con un tempo di sonno banalmente piccolo. Ciò farà sì che il thread di elaborazione dei messaggi restituisca il tempo della CPU ad altri thread. La CPU non sarà più fissata al 100%, ma è comunque piuttosto dispendiosa.

Vorrei utilizzare una libreria di messaggistica semplice e leggera chiamata ZeroMQ ( http://www.zeromq.org/ ). È una libreria open source (LGPL). Questa è una biblioteca molto piccola; sul mio server, l'intero progetto si compila in circa 60 secondi.

ZeroMQ semplifica enormemente il codice guidato dagli eventi, ed è anche la soluzione più efficiente in termini di prestazioni. La comunicazione tra thread utilizzando ZeroMQ è molto più rapida (in termini di velocità) rispetto all'utilizzo di semafori o socket UNIX locali. ZeroMQ è anche una soluzione portatile al 100%, mentre tutte le altre soluzioni assocerebbero il codice a un sistema operativo specifico.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top