2009-03-18 2 views
48

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

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% и practicaly бесполезно. Теперь, как я могу реализовать такой цикл событий, который реагирует, не потребляя процессор вообще?

Ответы приветствуются в Python и/или C++. Благодарю.

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

+3

Я полагаю, вы можете углубиться в исходный код Qt и увидеть именно то, что они делают в Exec(). Это, вероятно, даст вам несколько хороших указателей. – JimDaniel

ответ

62

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

Графический интерфейс Основной цикл выглядит следующим образом, в псевдо-коде:

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

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

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

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

Прошу представить дополнительную информацию!

+0

Как бы реализовать такую ​​систему ожидания, чтобы ждать не системных сигналов, а собственных сигналов? – fengshaun

+0

Как я уже сказал, ваш код работает только в ответ на события. Поэтому, если вы запускаете свое собственное событие, вы сделаете это как реакцию на какое-то системное событие. И тогда становится ясно, что на самом деле вам не нужна система событий для ваших пользовательских событий. Просто позвоните обработчикам прямо! – 2009-03-18 17:24:38

+0

Например, рассмотрим сигнал «Button :: clicked». Он будет срабатывать только в ответ на системное событие (выключение левой кнопки мыши). Таким образом, ваш код становится «virtual void Button :: handleLeftRelease (Point) {clicked.invoke();}», без необходимости в потоках или очереди событий или что-то еще. – 2009-03-18 17:27:09

11

Вообще-то я хотел бы сделать это с какой-то counting semaphore:

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

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

+0

Это звучит соблазнительно, мне нужно больше узнать о потоковом использовании. Благодарю. – fengshaun

+0

, так что нам нужен другой поток, который оживает? –

+0

@FallingFromBed - не ожидание, а блокировка ожидания на sepmaphore. Разница важна, потому что ожидания блокировки не будут потреблять процессорное время. –

10

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

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

+3

Ничего себе, это круто. –

21

Python:

Вы можете посмотреть на реализацию Twisted reactor, которая, вероятно, является лучшей реализацией для цикла событий в питона. Реакторы в Twisted являются реализациями интерфейса, и вы можете указать реактор типа для запуска: select, epoll, kqueue (все на основе c api с использованием этих системных вызовов), есть также реакторы на основе наборов QT и GTK.

Простая реализация будет использовать выбор:

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