Подтвердить что ты не робот

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

Если вы работали с инструментами 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?)
    }
}

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

Ответы приветствуются в Python и/или С++. Спасибо.

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

4b9b3361

Ответ 1

Я часто задавался вопросом о том же!

Основной цикл 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. Фактические обработчики получаются по-разному, например m_xConnection может быть взято из:: XConnectionNumber(). X11 также предоставляет высокоуровневый переносимый API для этого -:: XNextEvent() - но если бы вы использовали это, вы не смогли бы ждать нескольких источников событий одновременно.

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

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

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

Ответ 2

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() 

Ответ 3

В общем, я бы сделал это с помощью подсчета семафора:

  • Семафор начинается с нуля.
  • Цикл событий ожидает семафора.
  • Приходят события (ы), семафор увеличивается.
  • Обработчик событий разблокирует и уменьшает семафор и обрабатывает событие.
  • Когда все события обрабатываются, семафор равен нулю и блокирует цикл цикла событий.

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

Ответ 4

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

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