Как бы вы реализовали базовый цикл событий?


Если вы работали с gui toolkits, вы знаете, что есть цикл событий/основной цикл, который должен быть выполнен после того, как все будет сделано, и что будет держать приложение живым и реагировать на различные события. Например, для Qt вы бы сделали это в main ():

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

который в этом случае, приложение.exec ()-это основной цикл приложения.

очевидным способом реализации такого рода цикла было бы:

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

но это ограничивает процессор до 100% и практически бесполезно. Теперь, как я могу реализовать такой цикл событий, который реагирует, не съедая процессор в целом?

ответы ценятся в Python и / или C++. Спасибо.

сноска: ради обучения я буду реализовывать свои собственные сигналы / слоты, и я буду использовать их для генерации пользовательских событий (например,go_forward_event(steps)). Но если вы знаете, как я могу использовать системные события вручную, я хотел бы знать об этом.

4   51  

4 ответа:

Я часто думаю о том же!

основной цикл 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;
        }
    }
}

Что такое "ожидания"? Ну, это зависит от системы. В UNIX это называется "файловый дескриптор", а" waitOnAll " - это системный вызов ::select. Так называемые vector<Waitable> это ::fd_set в UNIX, и" whatHappened " фактически запрашивается через FD_ISSET. Фактические waitable-handles приобретаются различными способами, например m_xConnection можно взять из :: XConnectionNumber (). X11 также предоставляет высокоуровневый переносимый API для этого--:: XNextEvent () -- но если бы вы использовали это, вы не смогли бы дождаться нескольких источников событий одновременно.

как работает блокировка? "waitOnAll" - это системный вызов, который говорит ОС, чтобы поставить процесс на "список ожидания". Это означает, что вам не предоставляется время процессора, пока не произойдет событие на одном из ожидаемых объектов. Это означает, что ваш процесс простаивает, потребляя 0% CPU. Когда происходит событие, ваш процесс будет кратко реагировать на него, а затем вернуться в состояние ожидания. GUI приложения тратят почти все их время на холостом ходу.

что происходит со всеми циклами процессора, пока вы спите? Зависит. Иногда другой процесс будет иметь для них применение. Если нет, то ваша ОС будет занят-петля ЦП, или положить его в временный режим пониженного энергопотребления и т. д.

пожалуйста, запросите дополнительную информацию!

Python:

вы можете посмотреть на реализацию витая реактора что, вероятно, является лучшей реализацией для цикла событий в python. Реакторы в Twisted-это реализации интерфейса, и вы можете указать тип реактора для запуска: select, epoll, kqueue (все на основе C 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() 

вообще, я бы сделал это с какой-то счетный семафор:

  1. семафор начинается с нуля.
  2. цикл событий ждет на семафоре.
  3. событие(Ы) входит, семафор увеличивается.
  4. обработчик событий разблокирует и уменьшает семафор и обрабатывает событие.
  5. когда все события обрабатываются, семафор равен нулю и цикл событий снова блокируется.

Если вы не хотите, чтобы получить это сложно, вы можете просто добавить вызов sleep () в свой цикл while с тривиально малым временем сна. Это приведет к тому, что ваш поток обработки сообщений уступит свое процессорное время другим потокам. Процессор больше не будет привязан к 100%, но это все еще довольно расточительно.

Я бы использовал простую, легкую библиотеку сообщений под названием ZeroMQ (http://www.zeromq.org/). это библиотека с открытым исходным кодом (LGPL). Это очень маленькая библиотека; на моем сервере весь проект компилируется примерно за 60 секунд.

ZeroMQ значительно упростит ваш событийный код, и это также самое эффективное решение с точки зрения производительности. Связь между потоками с помощью ZeroMQ намного быстрее (с точки зрения скорости), чем с помощью семафоров или локальных сокет UNIX. ZeroMQ также является 100% портативным решением, в то время как все остальные решения будут привязывать ваш код к конкретной операционной системе.