erlang 为什么UI框架必须/应该是单线程的?

brvekthn  于 2022-12-08  发布在  Erlang
关注(0)|答案(3)|浏览(205)

密切相关的问题之前已经被问过:

但这些问题的答案仍然让我在一些问题上不清楚。

  • 第一个问题的提问者问多线程是否有助于提高性能,回答者大多说不会,因为GUI不太可能成为现代硬件上2D应用程序的瓶颈。但在我看来,这似乎是一种狡猾的辩论策略。当然,如果您仔细地构建了应用程序,使其只在UI线程上执行UI调用,那么就不会出现瓶颈。但是,这可能需要大量的工作,并使您的代码更加复杂,如果您有一个更快的核心或可以从多个线程进行UI调用,也许这将不值得这样做。
  • 一个普遍提倡的架构设计是让视图组件没有回调,并且除了它们的后代之外,不需要锁定任何东西。在这样的架构下,难道你不能让任何线程调用视图对象上的方法,使用每个对象的锁,而不用担心死锁吗?
  • 我对UI控件的情况不太有信心,但是只要它们的回调只由系统调用,为什么它们会导致任何 * 特殊 * 死锁问题呢?毕竟,如果回调需要做任何耗时的事情,它们将委托给另一个线程,然后我们就回到了多线程的情况。
  • 如果您可以在UI线程上阻塞,那么您将从多线程UI中获得多少好处?因为各种新兴的异步抽象技术实际上允许您这样做。
  • 我所看到的几乎所有讨论都假设并发将使用手动锁定来处理,但有一个广泛的共识,即手动锁定在大多数上下文中是管理并发的一种糟糕的方式。当我们考虑到Maven建议我们更多使用的并发原语时,讨论会有什么变化,比如软件事务内存,还是避开共享内存而支持消息传递(可能使用同步,如go)?
bbmckpt7

bbmckpt71#

TL;DR

这是一种简单的方法,可以强制在Activity中进行排序,而Activity最终无论如何都将按顺序进行(屏幕按顺序每秒绘制X次)。

讨论

处理在系统内具有单个身份的长期持有的资源通常通过用单个线程、进程、“对象”或在给定语言中表示关于并发性的原子单元的任何其他东西来表示它们来完成。回到非空的、疏忽的内核、非时间共享的一个真正的线程天,这是手动管理的轮询/循环或编写自己的调度系统。在这样的系统中,你仍然有一个1::1Map之间的功能/对象/thingy和单一的资源(或你疯了之前,8年级)。
这与处理网络套接字或任何其他长寿命资源的方法相同。GUI本身只是典型程序管理的许多此类资源之一,并且通常长寿命资源是事件排序很重要的地方。
例如,在一个聊天程序中,你通常不会写一个线程。你会有一个GUI线程,一个网络线程,也许还有其他一些处理日志资源或其他什么的线程。对于一个典型的系统来说,速度如此之快,以至于把日志和输入放在进行GUI更新的同一个线程中是很容易的,这并不罕见,但情况并不总是如此。尽管如此,在所有情况下,通过授予资源的单个线程,可以最容易地推断出资源的每一类别,这意味着一个线程用于网络,一个线程用于GUI,并且对于要管理的长期操作或资源而言,需要许多其他线程而不会阻塞其他线程。
为了使工作更容易,通常是尽可能地 * 不 * 在这些线程之间直接共享数据。队列比资源锁更容易推理,并且可以保证顺序。大多数GUI库要么将要处理的事件排队(因此可以按顺序对它们求值)或立即提交事件所需的数据更改,但是在重绘循环的每一遍之前锁定GUI的状态,在绘制屏幕时,唯一重要的事情是世界的状态 * 此时 *。这与典型的网络情况稍有不同,在典型的网络情况下,所有数据都需要按顺序发送,而忽略其中的一些数据是不可取的。
因此GUI框架本身并不是多线程的,GUI循环需要是一个单线程,以合理地管理单个长期占用的资源。编程示例,通常本质上是微不足道的,通常是单线程的,所有的程序逻辑都在与GUI循环相同的进程/线程中运行,但这在更复杂的程序中并不常见。

总而言之

因为调度很难,共享数据管理更难,而且一个资源只能被串行访问,所以用一个线程来表示每个长时间占用的资源和每个长时间运行的过程是一种典型的结构化代码的方式。GUI只是一个典型程序将要管理的多个资源中的一个。所以“GUI程序”绝不是单线程的,但GUI库通常是单线程的。
在简单的程序中,将其他程序逻辑放在GUI线程中并不会带来明显的损失,但当遇到大量负载或资源管理需要大量阻塞或轮询时,这种方法福尔斯,这就是为什么您经常会看到事件队列,信号槽消息抽象或其他多线程/处理方法,这些方法在几乎任何GUI库的角落都有提及(这里我包括游戏库--虽然游戏库通常期望您围绕自己的UI概念构建自己的小部件,但基本原理非常相似,只是级别低了一点)。
[As顺便说一句,我最近做了很多Qt/C++和Wx/Erlang.The Qt docs do a good job of explaining approaches to multi-threading,GUI循环的作用,以及Qt的信号/插槽方法在哪里适合抽象Erlang本身就是并发的,但是wx itself is typically started as a single OS process that manages a GUI update loop和Erlang将更新事件作为消息发送给它,并且GUI事件作为消息发送到Erlang端--这样就允许正常的Erlang并发编码,但是提供了GUI事件排序的单点,这样wx就可以完成它的GUI更新循环。]

chy5wohz

chy5wohz2#

因为GUI主线程代码太老了。非常老,因此非常适合低资源使用。如果有人从头开始写所有东西(即使是最新的GUI操作系统Android也没有),它会运行得很好,在多线程方面会更好。
例如,对MT有帮助的两个最佳改进是
1.现在我们有了MVVM(模型-视图-视图模型)模式,这是一个额外的数据复制。当开发工具包的时候,甚至在MVC中的一个单一的复制都是高度争论的。MVVM使多线程更容易。恕我直言,这是微软在.NET中首先发明它的主要原因,而不是数据绑定。
1.场景图方法。Android,iOS,Windows UWP(基于CoreWindow,在Windows 11项目重聚之前不存在),Gtk 4正在将GPU部分从模型中分离出来。是的,它现在实际上是一个MVVMGM(Model-View-ViewModel-GPUModel)。所以又是一个内存密集型层。如果你复制东西,你需要更少的同步。在Android和MacOS/上的SwiftUI上合并iOS现在使用GUI小部件的不变性来进一步改进这个View-〉GPUModel。
特别是对于GPU模型/场景图,GUI是单线程的说法不再正确。

kpbwa7wx

kpbwa7wx3#

据我所知,有两个原因:
推理单线程代码要容易得多;因此事件循环模型降低了错误的可能性。
2D用户界面不是CPU密集型的。一台旧电脑配上一张蹩脚的显卡就可以流畅地渲染所有你可能想要的窗口、框架、小部件等,而不会跳过一个节拍。
基本上,如果单线程代码更简单,错误更少,那么就应该选择单线程代码,而不是多线程代码,除非你迫切需要并行化或速度。典型的GUI框架没有这种需求。
当然,我们以前都经历过GUI应用程序的延迟和冻结。我认为绝大多数时候,这是开发人员的错:为应该异步处理的事件放置长时间运行的同步代码(这是所有主要UI框架都具有的机制)。

相关问题