【问题标题】:Should all event-driven frameworks be single-threaded?所有事件驱动的框架都应该是单线程的吗?
【发布时间】:2009-04-13 04:01:31
【问题描述】:

http://weblogs.java.net/blog/kgh/archive/2004/10/multithreaded_t.html 认为多线程 GUI 框架是一个失败的梦想。非 GUI 框架呢?这个经验法则是否适用于所有事件驱动的框架?

这是引起我注意的文章中的一句话:

输入事件处理的问题在于它倾向于以与大多数 GUI 活动相反的方向运行。通常,GUI 操作从库抽象堆栈的顶部开始,然后“向下”运行。我在我的应用程序中操作一个由一些 GUI 对象表达的抽象概念,所以我从我的应用程序开始,调用高级 GUI 抽象,调用低级 GUI 抽象,调用丑陋的内脏工具包,然后进入操作系统。相反,输入事件从操作系统层开始,并逐渐“向上”分派到抽象层,直到它们到达我的应用程序代码。

现在,由于我们使用抽象,我们自然会在每个抽象中单独进行锁定。不幸的是,我们遇到了经典的锁排序噩梦:我们有两种不同类型的活动想要以相反的顺序获取锁。所以僵局几乎是不可避免的。

【问题讨论】:

    标签: multithreading oop events


    【解决方案1】:

    没有。即使根据经验,它只是在说“很难让它们工作”。但是事件驱动框架非常类似于事件驱动模拟和其他各种东西。 Javaworld 中的难点与多线程无关,而是与 Java 中可用的抽象有关。

    在另一个环境中,比如 Erlang,它既相当自然,又相当健壮。

    更新

    听起来他仍然有错误的抽象。我看不出问题中存在任何导致锁定问题的内在因素。我想,关键在这里:

    现在,由于我们使用抽象, 我们自然会做锁定 在每个抽象中分开。 不幸的是,我们有经典 订购锁的噩梦:我们有两个 不同种类的活动正在进行 想要在对面获得锁的人 订单。所以死锁几乎 不可避免的。

    那么为什么死锁几乎是不可避免的呢?因为有两种不同的活动正在进行,它们想要以相反的顺序获取锁。那是因为“我们自然会在每个抽象中单独进行锁定。”

    换句话说,他选择了——或者环境为他选择了——不支持他需求的抽象。因此,他似乎声称,因此没有抽象会这样做。我觉得这没有说服力。我将从检查两件事开始:

    • 自然我们将在抽象中单独进行锁定”,并且
    • “我们正在发生一些事件,想要以相反的顺序获取锁。”

    根据我的经验,“自然 X”通常意味着“我还没有真正寻找其他选择”。而且我非常怀疑事件想要以相反的顺序获取锁;你得到一个锁,你做一些事情,然后你释放它。

    关键是,他似乎在提出这样一个事实,即他发现很难提出一个确实作为定理工作的方案来说明没有方案可以 工作。

    如果没有关于该问题的更多详细信息,很难构建一个反例,但正如我上面所说,有很多系统示例会以异步方式从内部流程和 GUI 发生事件,它设法拥有许多并发控制线程并且不会死锁。之所以想到 Erlang,是因为其中一类问题,即电话,正是 Erlang 被发明的原因,而事实上,这些问题正是为什么发明了 Erlang。

    【讨论】:

    • 你错误地将 Erlang 进程和线程等同起来。 Erlang 的人不这样做,并且存在一些非常根本的差异(例如,所有使 Erlang 适合并行处理的差异!)。
    • ...不是它们如何生成的实现细节。 Erlang 是一个很好的例子,说明了一组更好、更具表现力的原语如何使并发编程更容易、更可靠。
    • 因为 Erlang 是一种完全并发的函数式语言,并且(因为它没有可变变量)无法产生相同类型的死锁。但是,您真正的意思是“我对 Erlang 了解得不够多,无法理解”。所以见erlang.org
    • @Gili,你只能在一个答案中写下这么多。另一方面,您所说的与“使用我想要的特定设置,这种特别的抽象在 Java 中无法实现,因此所有多线程事件驱动框架都非常困难”是同构的。
    • 像所有“for all”表达式一样,反例的存在会证伪断言。并不是所有的事件驱动框架都应该是单线程的。确实,它们比多线程更难,尤其是在框架结构不适合的情况下。
    【解决方案2】:

    我不得不说不,但有一点需要注意。围绕共享状态的事件驱动框架,例如 UI,应该是单线程的。以通知为中心的事件驱动框架,例如机械监控(例如,让您知道管道中的压力何时过高),可以是单线程的,但可能更适合多线程。

    当然可以构建多线程的 UI 框架,我自己也这样做过。最后,我将其转换为单线程。部分原因确实属于查理所说的“这太难了”。问题在于,对于多线程 UI 框架,处理线程的不仅仅是 me,而是任何使用 UI 的人。内核当然是线程安全的,但是任何编写控件的人都必须使 那个 线程也安全。没关系,在对 UI 进行多项更改时,您必须通知核心您正在这样做,这样您就不会得到部分更新。由于用户通常是一个非常慢的东西,因此任何性能提升都可以忽略不计,如果需要特定情况,可以使用异步调用来解决。单线程实际上是这里合适的模型。

    另一方面,在没有(或很少)共享状态的模型中,多线程模型非常有意义。没有理由让一个事件(反应堆着火)延迟 30 秒,因为您的查询(Bob 操作员刚刚打卡)超时,因为一些 yahoo 在操作中锁定了 punch_card 表。

    【讨论】:

    • 我不明白你对“共享状态”的定义。为什么你会说 UI 框架具有共享状态而其他框架没有?我没有看到关于 UI 框架的“共享”内容。您指的是 UI 组件倾向于相互交互这一事实吗?
    • 我主要指的是实际的窗口本身。在用户操作的 UI 中,您通常会执行一些活动(这可能是独立的,如果长时间运行,则可能是线程化的),然后更新 UI。最终共享的是 UI 的绘图表面。
    【解决方案3】:

    我想知道问题有多少来自多线程不适合事件处理这一事实,以及许多构建 GUI 的开发人员不精通多线程这一事实。

    我倾向于支持后一种情况,并且也支持某些框架的笨拙。例如,我发现在 Eclipse 中编写 GUI 代码相当烦人,因为没有更方便的方法来处理繁重的逻辑和 GUI 代码。

    【讨论】:

      【解决方案4】:

      是和否。

      事件驱动框架通常允许您执行通常需要多个线程和 1 个线程的事情。这就是为什么它们通常是单线程的。

      但是,在任何定义中,事件驱动框架都不能将事件分派给多个线程。

      【讨论】:

        【解决方案5】:

        这个经验法则根本不适用于非 GUI 框架。请参阅 Welsh 的 SEDA 架构,请参阅 yield (code.google.com/p/yield) 以获得良好的 python/c++ 混合实现。

        【讨论】:

        • 好点!你所说的对应用服务器来说是有意义的,因为每个连接都完全独立于另一个。我猜他写的内容适用于事件生成器以某种方式相互关联的任何框架。
        【解决方案6】:

        一般来说,是的,因为事件驱动框架类似于 Reactor 模式,它规定了一个等待事件的主循环(“事件循环”,然后为各种元素调用注册的回调。

        这就是传统上定义事件驱动框架的方式。 下面是关于 Erlang 进程如何与 VM 的事件循环相关联的一个很好的描述:

        Erlang 进程与 Erlang VM 的事件驱动网络 IO 核心紧密集成。进程可以“拥有”套接字并向套接字发送和接收消息。这提供了面向并发编程的优雅以及事件驱动 IO 的可扩展性(Erlang VM 在幕后使用 epoll/kqueue)。

        http://yarivsblog.com/articles/2008/05/18/erlang-vs-scala/

        更新:这里有一些有趣的笔记,当时对我影响很大。请特别注意锁和回调如何糟糕地交互。

        http://home.pacbell.net/ouster/threads.pdf

        【讨论】:

        • 反应器模式适用于特定情况,即将对象编组/解组到/从网络。人们普遍认为,当今操作系统的最佳性能是通过在单线程中进行网络 IO 获得的。没有一般原因...
        • ... 事件驱动应用程序的其余部分需要是单线程的。
        • 我不确定一个普遍的原因,但似乎大多数项目在尝试集成线程时都会遇到一种或另一种问题,并且通常会退回到删除线程。只是我的观察!
        猜你喜欢
        • 2012-10-01
        • 1970-01-01
        • 2018-01-03
        • 2015-09-04
        • 2020-03-05
        • 2015-10-27
        • 2020-06-08
        相关资源
        最近更新 更多