If you have worked with gui toolkits, you know that there is a event-loop/main-loop that should be executed after everything is done, and that will keep the application alive and responsive to different events. For example, for Qt, you would do this in main():
如果您使用过gui工具包,那么您就知道有一个事件循环/主循环应该在所有操作完成之后执行,这将使应用程序保持活动状态并对不同的事件作出响应。例如,对于Qt,您可以在main()中这样做:
int main() {
QApplication app(argc, argv);
// init code
return app.exec();
}
Which in this case, app.exec() is the application's main-loop.
在本例中,app.exec()是应用程序的主循环。
The obvious way to implement this kind of loop would be:
实现这种循环的明显方法是:
void exec() {
while (1) {
process_events(); // create a thread for each new event (possibly?)
}
}
But this caps the CPU to 100% and is practicaly useless. Now, how can I implement such an event loop that is responsive without eating the CPU altogether?
但这将CPU限制在100%,实际上是无用的。现在,我如何实现这样一个响应速度快的事件循环,而不需要完全消耗CPU ?
Answers are appreciated in Python and/or C++. Thanks.
在Python和/或c++中,答案是值得欣赏的。谢谢。
Footnote: For the sake of learning, I will implement my own signals/slots, and I would use those to generate custom events (e.g. go_forward_event(steps)
). But if you know how I can use system events manually, I would like to know about that too.
脚注:为了学习,我将实现自己的信号/插槽,我将使用它们来生成自定义事件(例如go_forward_event(步骤))。但是如果您知道我如何手动使用系统事件,我也想知道这一点。
4 个解决方案
#1
63
I used to wonder a lot about the same!
我过去常常对同一件事感到奇怪!
A GUI main loop looks like this, in pseudo-code:
在伪代码中,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;
}
}
}
What is a "Waitable"? Well, it's system dependant. On UNIX it's called a "file descriptor" and "waitOnAll" is the ::select system call. The so-called vector<Waitable>
is a ::fd_set
on UNIX, and "whatHappened" is actually queried via FD_ISSET
. The actual waitable-handles are acquired in various ways, for example m_xConnection
can be taken from ::XConnectionNumber(). X11 also provides a high-level, portable API for this -- ::XNextEvent() -- but if you were to use that, you wouldn't be able to wait on several event sources simultaneously.
“可”是什么?它是系统的依赖。在UNIX上,它被称为“文件描述符”,“waitOnAll”是::select system call。所谓的vector
How does the blocking work? "waitOnAll" is a syscall that tells the OS to put your process on a "sleep list". This means you are not given any CPU time until an event occurs on one of the waitables. This, then, means your process is idle, consuming 0% CPU. When an event occurs, your process will briefly react to it and then return to idle state. GUI apps spend almost all their time idling.
阻塞是如何工作的?“waitOnAll”是一个syscall,它告诉操作系统将您的进程放在“睡眠列表”中。这意味着在事件发生在一个waitables上之前,不会给您任何CPU时间。这意味着您的进程是空闲的,消耗了0%的CPU。当一个事件发生时,您的进程将对它进行短暂的反应,然后返回到空闲状态。GUI应用程序几乎所有时间都在闲置。
What happens to all the CPU cycles while you're sleeping? Depends. Sometimes another process will have a use for them. If not, your OS will busy-loop the CPU, or put it into temporary low-power mode, etc.
当你睡觉时,所有的CPU周期会发生什么变化?视情况而定。有时,另一个过程也会对它们有用。如果没有的话,你的操作系统会忙着循环CPU,或者把它放到临时的低功耗模式中,等等。
Please ask for further details!
详情请咨询!
#2
21
Python:
Python:
You can look at the implementation of the Twisted reactor which is probably the best implementation for an event loop in python. Reactors in Twisted are implementations of an interface and you can specify a type reactor to run: select, epoll, kqueue (all based on a c api using those system calls), there are also reactors based on the QT and GTK toolkits.
您可以查看Twisted reactor的实现,它可能是python中事件循环的最佳实现。Twisted matrix中的反应堆是接口的实现,您可以指定要运行的类型反应堆:select、epoll、kqueue(都基于使用这些系统调用的c api),还有基于QT和GTK工具包的反应堆。
A simple implementation would be to use select:
一个简单的实现将是使用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
11
Generally I would do this with some sort of counting semaphore:
通常我会用一些计数信号量来做:
- Semaphore starts at zero.
- 信号从0开始。
- Event loop waits on semaphore.
- 事件循环等待信号量。
- Event(s) come in, semaphore is incremented.
- 事件进入,信号量增加。
- Event handler unblocks and decrements the semaphore and processes the event.
- 事件处理程序对信号量进行解压并处理事件。
- When all events are processed, semaphore is zero and event loop blocks again.
- 当所有事件被处理时,信号量为0,事件循环再次阻塞。
If you don't want to get that complicated, you could just add a sleep() call in your while loop with a trivially small sleep time. That will cause your message processing thread to yield it's CPU time to other threads. The CPU won't be pegged at 100% any more, but it's still pretty wasteful.
如果您不想弄得那么复杂,您可以在while循环中添加一个sleep()调用,而睡眠时间很少。这将导致消息处理线程将其CPU时间分配给其他线程。CPU不会被100%的固定,但是仍然是非常浪费的。
#4
10
I would use a simple, light-weight messaging library called ZeroMQ (http://www.zeromq.org/). It is an open source library (LGPL). This is a very small library; on my server, the whole project compiles in about 60 seconds.
我将使用一个简单的轻量级消息库ZeroMQ (http://www.zeromq.org/)。它是一个开源库(LGPL)。这是一个很小的图书馆;在我的服务器上,整个项目在大约60秒内编译。
ZeroMQ will hugely simplify your event-driven code, AND it is also THE most efficient solution in terms of performance. Communicating between threads using ZeroMQ is much faster (in terms of speed) than using semaphores or local UNIX sockets. ZeroMQ also be a 100% portable solution, whereas all the other solutions would tie your code down to a specific operating system.
ZeroMQ将极大地简化您的事件驱动代码,在性能方面它也是最有效的解决方案。使用ZeroMQ在线程之间通信要比使用信号量或本地UNIX套接字快得多(就速度而言)。ZeroMQ也是一个100%可移植的解决方案,而所有其他解决方案都将您的代码绑定到特定的操作系统上。
#1
63
I used to wonder a lot about the same!
我过去常常对同一件事感到奇怪!
A GUI main loop looks like this, in pseudo-code:
在伪代码中,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;
}
}
}
What is a "Waitable"? Well, it's system dependant. On UNIX it's called a "file descriptor" and "waitOnAll" is the ::select system call. The so-called vector<Waitable>
is a ::fd_set
on UNIX, and "whatHappened" is actually queried via FD_ISSET
. The actual waitable-handles are acquired in various ways, for example m_xConnection
can be taken from ::XConnectionNumber(). X11 also provides a high-level, portable API for this -- ::XNextEvent() -- but if you were to use that, you wouldn't be able to wait on several event sources simultaneously.
“可”是什么?它是系统的依赖。在UNIX上,它被称为“文件描述符”,“waitOnAll”是::select system call。所谓的vector
How does the blocking work? "waitOnAll" is a syscall that tells the OS to put your process on a "sleep list". This means you are not given any CPU time until an event occurs on one of the waitables. This, then, means your process is idle, consuming 0% CPU. When an event occurs, your process will briefly react to it and then return to idle state. GUI apps spend almost all their time idling.
阻塞是如何工作的?“waitOnAll”是一个syscall,它告诉操作系统将您的进程放在“睡眠列表”中。这意味着在事件发生在一个waitables上之前,不会给您任何CPU时间。这意味着您的进程是空闲的,消耗了0%的CPU。当一个事件发生时,您的进程将对它进行短暂的反应,然后返回到空闲状态。GUI应用程序几乎所有时间都在闲置。
What happens to all the CPU cycles while you're sleeping? Depends. Sometimes another process will have a use for them. If not, your OS will busy-loop the CPU, or put it into temporary low-power mode, etc.
当你睡觉时,所有的CPU周期会发生什么变化?视情况而定。有时,另一个过程也会对它们有用。如果没有的话,你的操作系统会忙着循环CPU,或者把它放到临时的低功耗模式中,等等。
Please ask for further details!
详情请咨询!
#2
21
Python:
Python:
You can look at the implementation of the Twisted reactor which is probably the best implementation for an event loop in python. Reactors in Twisted are implementations of an interface and you can specify a type reactor to run: select, epoll, kqueue (all based on a c api using those system calls), there are also reactors based on the QT and GTK toolkits.
您可以查看Twisted reactor的实现,它可能是python中事件循环的最佳实现。Twisted matrix中的反应堆是接口的实现,您可以指定要运行的类型反应堆:select、epoll、kqueue(都基于使用这些系统调用的c api),还有基于QT和GTK工具包的反应堆。
A simple implementation would be to use select:
一个简单的实现将是使用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
11
Generally I would do this with some sort of counting semaphore:
通常我会用一些计数信号量来做:
- Semaphore starts at zero.
- 信号从0开始。
- Event loop waits on semaphore.
- 事件循环等待信号量。
- Event(s) come in, semaphore is incremented.
- 事件进入,信号量增加。
- Event handler unblocks and decrements the semaphore and processes the event.
- 事件处理程序对信号量进行解压并处理事件。
- When all events are processed, semaphore is zero and event loop blocks again.
- 当所有事件被处理时,信号量为0,事件循环再次阻塞。
If you don't want to get that complicated, you could just add a sleep() call in your while loop with a trivially small sleep time. That will cause your message processing thread to yield it's CPU time to other threads. The CPU won't be pegged at 100% any more, but it's still pretty wasteful.
如果您不想弄得那么复杂,您可以在while循环中添加一个sleep()调用,而睡眠时间很少。这将导致消息处理线程将其CPU时间分配给其他线程。CPU不会被100%的固定,但是仍然是非常浪费的。
#4
10
I would use a simple, light-weight messaging library called ZeroMQ (http://www.zeromq.org/). It is an open source library (LGPL). This is a very small library; on my server, the whole project compiles in about 60 seconds.
我将使用一个简单的轻量级消息库ZeroMQ (http://www.zeromq.org/)。它是一个开源库(LGPL)。这是一个很小的图书馆;在我的服务器上,整个项目在大约60秒内编译。
ZeroMQ will hugely simplify your event-driven code, AND it is also THE most efficient solution in terms of performance. Communicating between threads using ZeroMQ is much faster (in terms of speed) than using semaphores or local UNIX sockets. ZeroMQ also be a 100% portable solution, whereas all the other solutions would tie your code down to a specific operating system.
ZeroMQ将极大地简化您的事件驱动代码,在性能方面它也是最有效的解决方案。使用ZeroMQ在线程之间通信要比使用信号量或本地UNIX套接字快得多(就速度而言)。ZeroMQ也是一个100%可移植的解决方案,而所有其他解决方案都将您的代码绑定到特定的操作系统上。