为什么 must/should UI 框架是单线程的?

Why must/should UI frameworks be single threaded?

之前曾问过密切相关的问题:

但是这些问题的答案还是让我有些不清楚。

TL;DR

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

讨论

处理在系统中具有单一身份的长期持有资源通常是通过用单个线程、进程、"object" 或任何其他代表给定并发性的原子单元来表示的。语。回到非占用、疏忽内核、非分时共享、One True Thread 时代,这是由 polling/cycling 手动管理或编写您自己的调度系统。在这样的系统中,你仍然有 function/object/thingy 和单一资源之间的 1::1 映射(或者你在 8 年级之前就发疯了)。

这与处理网络套接字或任何其他长期资源所使用的方法相同。 GUI 本身只是典型程序管理的许多此类资源之一,通常长期存在的资源是事件顺序很重要的地方。

例如,在聊天程序中,您通常不会编写单线程。你会有一个 GUI 线程,一个网络线程,也许还有其他一些处理日志资源或其他的线程。对于一个典型的系统来说,速度如此之快以至于将日志记录和输入放入进行 GUI 更新的同一个线程中更容易,这种情况并不少见,但情况并非总是如此。但是,在所有情况下,通过为它们分配一个线程,最容易推理出每一类资源,这意味着一个线程用于网络,一个线程用于 GUI,但是还需要许多其他线程在不阻塞其他操作或资源的情况下管理长期存在的操作或资源。

为了让生活更轻松,通常不要尽可能在这些线程之间直接共享数据。队列比资源锁更容易推理,并且可以保证顺序。大多数 GUI 库要么将要处理的事件排队(以便按顺序评估),要么立即提交事件所需的数据更改,但在每个之前锁定 GUI 的状态通过重绘循环。之前发生了什么并不重要,绘制屏幕时唯一重要的是当时的世界状态。这与典型的网络情况略有不同,在典型的网络情况下,所有数据都需要按顺序发送,而忘记其中一些数据是不可取的。

所以 GUI 框架不是多线程的,就其本身而言,GUI 循环需要是一个单线程才能理智地管理单个长期持有的资源。编程示例,通常本质上是微不足道的,通常是单线程的,所有程序逻辑 运行 在与 GUI 循环相同的 process/thread 中,但这在更复杂的情况下并不典型程序。

总结

由于调度难,共享数据管理更难,而且单个资源无论如何只能串行访问,单个线程用来表示每个long-held资源和每个long-运行过程一种典型的结构代码方式。 GUIs 只是典型程序将管理的几种资源中的一种。所以 "GUI programs" 绝不是单线程的,但 GUI 库通常是。

在普通程序中,将其他程序逻辑放在 GUI 线程中并没有实际的惩罚,但是当遇到大量负载或资源管理需要大量阻塞或轮询时,这种方法就会失效,这就是为什么您经常会在几乎所有 GUI 库的尘土飞扬的角落中看到事件队列、信号槽消息抽象或其他 multi-threading/processing 方法的原因(这里我包括游戏库——虽然游戏库通常希望您基本上围绕自己的 UI 概念构建自己的小部件,但基本原则非常相似,只是级别较低)。

[顺便说一句,我最近做了很多 Qt/C++ 和 Wx/Erlang。 The Qt docs do a good job of explaining approaches to multi-threading, the role of the GUI loop, and where Qt's signal/slot approach fits into the abstraction (so you don't have to think about concurrency/locking/sequencing/scheduling/etc very much). Erlang is inherently concurrent, but wx itself is typically started as a single OS process that manages a GUI update loop 和 Erlang 将更新事件作为消息发送给它,GUI 事件作为消息发送到 Erlang 端——因此允许正常的 Erlang 并发编码,但提供 G[=55 的单点=] 事件排序,以便 wx 可以执行其 GUI 更新循环。]

因为GUI主线程代码比较旧。非常古老,因此非常专为低资源使用而设计。如果有人再次从头开始编写所有内容(甚至 Android 因为最近的 GUI OS 没有)它会工作得很好并且在多线程中会更好。

例如,对 MT 有帮助的两个最佳改进是

  1. 现在我们有了 MVVM (Model-View-ViewModel) 模式,这是一个额外的数据重复。当开发工具包时,即使是 MVC 中的单个重复也引起了激烈的争论。 MVVM 使多线程更容易。恕我直言,这是 Microsoft 首先在 .NET 中发明它的主要原因,而不是数据绑定。

  2. 场景图方法。 Android、iOS、Windows UWP(基于 CoreWindow 而不是 hWnd,直到 Windows11 Project Reunion),Gtk4 正在将 GPU 部分与模型解耦。是的,它现在实际上是一个 MVVMGM(模型-视图-视图模型-GPU 模型)。所以另一个内存密集层。如果你复制东西,你需要更少的同步。 Android 上的 Combine 和 MacOS/iOS 上的 SwiftUI 现在正在使用 GUI 小部件的不变性来进一步改进这个 View->GPUModel。

特别是对于 GPU Model/Scene Graph,GUI 是单线程的说法不再正确。