事件循环是否只是具有优化轮询的for / while循环?


55

我试图了解什么是事件循环。通常的解释是,在事件循环中,您会做一些事情,直到收到事件发生的通知。然后,您可以处理事件并继续做之前的工作。

用示例映射以上定义。我有一台在事件循环中“侦听”的服务器,当检测到套接字连接时,将读取并显示其中的数据,此后服务器将像以前一样继续/开始监听。


但是,此事件正在发生,而我们收到的通知“就像那样”对我来说就很大了。您可以说:“注册事件侦听器不只是那样而已”。但是什么是事件侦听器,但由于某种原因没有返回的函数。它是否在自己的循环中,等待事件发生时得到通知?事件监听器是否还应该注册一个事件监听器?它在哪里结束?


事件是可以使用的很好的抽象,但是仅仅是一个抽象。我认为,最后不可避免地要进行投票。也许我们没有在代码中执行此操作,但是较低级别(编程语言实现或OS)正在为我们执行此操作。

它基本上可以归结为以下伪代码,这些伪代码在足够低的位置运行,因此不会导致繁忙的等待:

while(True):
    do stuff
    check if event has happened (poll)
    do other stuff

这是我对整个想法的理解,我想听听这是否正确。我乐于接受整个想法从根本上是错误的,在这种情况下,我希望有正确的解释。


3
事件系统是Observer模式的实现。了解模式应该巩固您对事件的理解。不需要轮询。
史蒂文·埃弗斯

1
最终是的,即使语言构造将其抽象化。
2013年

@SteveEvers在您的Wiki链接中。什么是EventSource如果不轮询键盘输入干什么?
TheMeaningfulEngineer 2013年

@Alan:它可以做任何事情,但是特别是对于键盘输入,确实存在用于将应用程序注册为侦听键盘事件的API,然后您可以将它们用作事件源,而无需进行轮询。当然,如果跌落得足够远,USB 总是在轮询,但让我们假设我们使用的是中断驱动的PS / 2键盘,然后您便有了基于零轮询事件的键盘输入堆栈。
Phoshi

多年来,我一直在问这个问题,但是我无法说出为什么我从不问这个问题。谢谢,我现在对@Karl Bielefeldt的理解感到满意。
0xc0de

Answers:


54

如果没有事件准备就绪,大多数事件循环将挂起,这意味着操作系统将不给任务任何执行时间,直到事件发生为止。

说事件是一个按键。您可能会问,操作系统中是否有循环检查按键。答案是不。按下按键会产生一个中断,该中断由硬件异步处理。同样对于计时器,鼠标​​移动,数据包到达等。

实际上,对于大多数操作系统而言,轮询事件是抽象的。硬件和操作系统以异步方式处理事件,并将其放入可以由应用程序轮询的队列中。您只会在嵌入式系统的硬件级别上看到真正的轮询,甚至并非总是如此。


4
中断不是改变电线上的电压吗?可以触发事件本身吗?还是必须对引脚进行电压值查询?
TheMeaningfulEngineer 2013年

8
那可以自己触发一个事件。处理器就是这样设计的。实际上,可以通过中断将处理器从睡眠中唤醒。
Karl Bielefeldt

2
我对这一说法感到困惑Most event loops will block。这与“与使用线程相反,使用非阻塞异步调用的事件循环范例”如何适应?
TheMeaningfulEngineer 2013年

1
如果查看GTK +之类的库的事件循环,它们将检查新事件,然后在循环中调用事件处理程序,但是如果没有任何事件,它们将阻塞信号量,计时器或其他东西。各个开发人员创建自己的事件循环,这些事件循环不会在空的事件队列中阻塞,但是广泛使用的库都会阻塞。否则,事件循环效率太低。
Karl Bielefeldt

1
我想您可以这样看,但是它是库和/或OS中的多线程而不是应用程序代码。事件循环为应用程序开发人员消除了同步问题。
Karl Bielefeldt

13

我认为事件侦听器不是在运行自己的循环的函数,而是在第一名赛跑者在等待发炮手的情况下进行的接力赛。使用事件而不是轮询的一个重要原因是它们在CPU周期上效率更高。为什么?从硬件(而不是源代码)的角度来看它。

考虑一个Web服务器。当服务器调用listen()和阻止时,您的代码将代替中继器。当新连接的第一个数据包到达时,网卡将通过中断操作系统来开始竞赛。操作系统运行一个中断服务程序(ISR)来抓取数据包。ISR将警棍传递到建立连接的更高级别的例程。连接有效后,该例程会将警棍传递到listen(),然后将警棍传递给您的代码。届时,您可以使用连接进行所需的操作。就我们所知,在比赛之间,每个接力运动员都可以去酒吧。事件抽象的优点是您的代码不必知道或关心。

某些操作系统包含事件处理代码,该代码将运行比赛的一部分,放开指挥棒,然后循环回到其起点,以等待下一场比赛开始。从这个意义上说,事件处理是在许多并发循环中优化轮询的。但是,总有一个外部触发器可以启动该过程。事件侦听器不是不返回的函数,而是在运行之前等待该外部触发器的函数。而不是:

while(True):
    do stuff
    check if event has happened (poll)
    do other stuff

我认为这是:

on(some event):    //I got the baton
     do stuff
     signal the next level up    //Pass the baton

和之间signal和处理程序下次运行时,有概念上运行或循环任何代码。


这很有意义,但是在等待中断时事件循环在做什么?如果阻塞了,那不是与事件循环相反的“多线程”方法吗?
TheMeaningfulEngineer

如果您的代码有循环,forever: { select(); do stuff; }那么您每次都会在循环中重新进入竞争。无论您是从单个线程重复执行此操作,还是在单独的线程或处理器上并行执行操作,我都将每个事件视为自己的比赛。例如,Web浏览器是一个多线程程序,在单独的线程中具有多个事件循环,其中至少有一个用于UI,每个下载的页面都有一个。我在编码时提出的问题是“如何足够快地处理事件?” 有时答案是一个循环,有时是线程,通常是一个组合。
cxw

我从事基于事件的编程已有二十多年了,我发现这个答案非常令人困惑。为了使侦听器正常工作,必须有一个循环等待事件,然后将其路由到所有已注册的侦听器。(假设我们在谈论软件而不是硬件中断)
Bryan Oakley,2013年

8

不。它不是“优化轮询”。事件循环使用中断驱动的I / O代替轮询。

While,直到,For等循环是轮询循环。

“轮询”是反复检查某些内容的过程。由于循环代码是连续执行的,并且由于它是一个小的“紧密”循环,因此处理器几乎没有时间切换任务和执行其他任何操作。当计算机无响应时,几乎所有“挂起”,“冻结”,“锁定”或任何您想调用的名称,都是代码被卡在意外轮询循环中的表现。检测将显示100%CPU使用率。

中断驱动的事件循环比轮询循环更有效。轮询是对CPU周期的极度浪费,因此会尽一切努力消除或最小化它。

但是,为了优化代码质量,大多数语言都尝试对事件处理命令尽可能使用轮询循环范式,因为它们在程序中的功能相似。因此,轮询是等待按键之类的更熟悉的方式,对于没有经验的人来说,很容易使用它并完成可能本身运行良好的程序,但是在运行时没有其他任何作用。它已经“接管”了机器。

如其他答案所述,在中断驱动的事件处理中,基本上在CPU内设置了一个“标志”,并且该进程被“挂起”(不允许运行),直到该标志被其他进程(例如键盘)更改为止当用户按下一个键时,驱动程序将其更改)。如果该标志是实际的硬件状况,例如线路被“拉高”,则称为“中断”或“硬件中断”。但是,大多数仅作为CPU或主内存(RAM)中的内存地址实现,并称为“信号量”。

信号量可以在软件的控制下进行更改,因此可以在软件过程之间提供非常快速,简单的信号机制。

但是,只能通过硬件来更改中断。中断的最普遍使用是内部时钟芯片按固定间隔触发的中断。由时钟中断激活的无数种软件操作之一是信号量的更改。

我遗漏了很多东西,但是不得不停在某个地方。请询问您是否需要更多详细信息。


7

通常,答案是硬件,不受控制的操作系统和后台线程,使它们看起来毫不费力。网卡接收到一些数据,它会引发一个中断以通知CPU。操作系统的中断处理程序对其进行处理。然后,操作系统将唤醒您无法控制的后台线程(该线程是通过注册事件创建的,并且自注册事件以来就一直处于休眠状态),作为处理事件的一部分,并运行事件处理程序。


7

我将与到目前为止所看到的所有其他答案背道而驰,说“是”。我认为其他答案使事情变得过于复杂。从概念上看,所有事件循环本质上都是:

while <the_program_is_running> {
    event=wait_for_next_event()
    process_event(event)
}

如果您是第一次尝试了解事件循环,则将它们视为简单循环不会有任何危害。一些基础框架正在等待OS传递事件,然后将事件路由到一个或多个处理程序,然后等待下一个事件,依此类推。从应用程序软件的角度来看,这实际上就是所有内容。


1
+1代表直截了当。但是重点是“等待”。该代码的执行将在循环的第一行停止,直到事件发生才继续执行。这与繁忙的轮询循环不同,繁忙的轮询循环反复检查事件,直到事件发生为止。
汤姆·潘宁

1

并非所有事件触发器都是在循环中处理的。我经常编写自己的事件引擎的方式如下:

interface Listener {
    void handle (EventInfo info);
}

List<Listener> registeredListeners

void triggerEvent (EventInfo info) {
    foreach (listener in registeredListeners) { // Memo 1
        listener.handle(info) // the handling may or may not be synchronous... your choice
    }
}

void somethingThatTriggersAnEvent () {
    blah
    blah
    blah
    triggerEvent(someGeneratedEventInfo)
    more blah
}

请注意,尽管备注1处于循环中,但该循环用于通知每个侦听器。事件触发器本身不一定处于循环中。

从理论上讲,只要OS公开了某种registerListenerAPI ,OS级别的键事件就可以使用相同的技术(尽管我认为它们经常进行轮询?我只是在这里推测)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.