你将如何实现一个基本的事件循环?
如果你已经使用了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?) } }
但是这将CPU限制到100%,实际上是无用的。 现在,我怎样才能实现这样一个响应而不用吃CPU的事件循环呢?
在Python和/或C ++中赞赏答案。 谢谢。
脚注:为了学习,我将实现自己的信号/插槽,并使用它们来生成自定义事件(例如, go_forward_event(steps)
)。 但是,如果您知道如何手动使用系统事件,我也想知道这一点。
我曾经很想知道很多关于同样的!
一个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>
在UNIX上是一个::fd_set
,实际上通过FD_ISSET
查询“whatHappened”。 实际的等待手柄是以各种方式获取的,例如m_xConnection
可以从:: XConnectionNumber()中获得。 X11还为这个 – :: XNextEvent()提供了一个高级的可移植的API,但是如果你使用它,你将无法同时等待几个事件源。
如何阻止工作? “waitOnAll”是一个系统调用,告诉操作系统把你的进程放在“睡眠列表”上。 这意味着,在某个等待事件发生之前,您不会获得任何CPU时间。 这意味着你的进程是空闲的,消耗0%的CPU。 当一个事件发生时,你的过程会短暂地对它做出反应,然后返回到空闲状态。 graphics用户界面应用程序几乎全部闲置。
当你睡觉的时候,所有的CPU周期会发生什么? 依靠。 有时候另一个进程会对他们有用处。 如果没有,你的操作系统会忙着循环CPU,或者进入暂时的低功耗模式等等。
请询问更多细节!
python:
你可以看看Twisted reactor的实现,它可能是python中事件循环的最佳实现。 Twisted中的Reactors是一个接口的实现,您可以指定一个typesreactor运行: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()
一般来说,我会用一些计数信号做这个:
- 信号量从零开始。
- 事件循环等待信号量。
- 事件进入,信号量增加。
- 事件处理程序解除和减less信号量并处理事件。
- 处理所有事件时,信号量为零,事件循环再次阻塞。
如果你不想这么复杂,你可以在while循环中添加一个sleep()调用,而且睡眠时间微不足道。 这将导致您的消息处理线程产生其他线程的CPU时间。 中央处理器不会再以100%的价格挂起来,但是这样做还是很浪费的。
我将使用一个名为ZeroMQ( http://www.zeromq.org/ )的简单轻量级消息传递库。 它是一个开源库(LGPL)。 这是一个非常小的图书馆; 在我的服务器上,整个项目在大约60秒内编译完成。
ZeroMQ将极大地简化您的事件驱动代码,并且在性能方面也是最有效的解决scheme。 使用ZeroMQ在线程之间进行通信要比使用信号量或本地UNIX套接字快得多(就速度而言)。 ZeroMQ也是一个100%的便携式解决scheme,而所有其他解决scheme将您的代码绑定到一个特定的操作系统。