为什么 must/should UI 框架是单线程的?
Why must/should UI frameworks be single threaded?
之前曾问过密切相关的问题:
- Why are most UI frameworks single threaded?.
- Should all event-driven frameworks be single-threaded?
但是这些问题的答案还是让我有些不清楚。
第一个问题提问者问多线程对性能有没有帮助,回答者大多说不会,因为GUI不太可能现代硬件上 2D 应用程序的瓶颈。但在我看来,这似乎是一种偷偷摸摸的辩论策略。当然,如果您仔细构建您的应用程序,除了在 UI 线程上调用 UI 之外什么都不做,您就不会有瓶颈。但这可能需要大量工作并使您的代码更复杂,如果您有更快的核心或可以从多个线程进行 UI 调用,也许不值得这样做。
一种普遍提倡的架构设计是让视图组件没有回调,并且除了它们的后代之外不需要锁定任何东西。在这样的架构下,难道不能让任何线程调用视图对象的方法,使用对象锁,而不用担心死锁吗?
我对UI控件的情况不太确定,但只要它们的回调只被系统调用,它们为什么会导致任何特殊 死锁问题?毕竟,如果回调需要做任何耗时的事情,它们将委托给另一个线程,然后我们又回到了多线程的情况。
如果您可以阻塞在 UI 线程上,您将获得多线程 UI 的多少好处?因为各种新兴的异步抽象实际上可以让你做到这一点。
我看到的几乎所有讨论都假设并发将使用手动锁定来处理,但人们普遍认为,在大多数情况下,手动锁定是管理并发的糟糕方法。当我们考虑到专家建议我们更多使用的并发原语时,讨论会发生什么变化,例如软件事务内存,或者避免共享内存以支持消息传递(可能使用同步,如在 go 中)?
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 有帮助的两个最佳改进是
现在我们有了 MVVM (Model-View-ViewModel) 模式,这是一个额外的数据重复。当开发工具包时,即使是 MVC 中的单个重复也引起了激烈的争论。 MVVM 使多线程更容易。恕我直言,这是 Microsoft 首先在 .NET 中发明它的主要原因,而不是数据绑定。
场景图方法。 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 是单线程的说法不再正确。
之前曾问过密切相关的问题:
- Why are most UI frameworks single threaded?.
- Should all event-driven frameworks be single-threaded?
但是这些问题的答案还是让我有些不清楚。
第一个问题提问者问多线程对性能有没有帮助,回答者大多说不会,因为GUI不太可能现代硬件上 2D 应用程序的瓶颈。但在我看来,这似乎是一种偷偷摸摸的辩论策略。当然,如果您仔细构建您的应用程序,除了在 UI 线程上调用 UI 之外什么都不做,您就不会有瓶颈。但这可能需要大量工作并使您的代码更复杂,如果您有更快的核心或可以从多个线程进行 UI 调用,也许不值得这样做。
一种普遍提倡的架构设计是让视图组件没有回调,并且除了它们的后代之外不需要锁定任何东西。在这样的架构下,难道不能让任何线程调用视图对象的方法,使用对象锁,而不用担心死锁吗?
我对UI控件的情况不太确定,但只要它们的回调只被系统调用,它们为什么会导致任何特殊 死锁问题?毕竟,如果回调需要做任何耗时的事情,它们将委托给另一个线程,然后我们又回到了多线程的情况。
如果您可以阻塞在 UI 线程上,您将获得多线程 UI 的多少好处?因为各种新兴的异步抽象实际上可以让你做到这一点。
我看到的几乎所有讨论都假设并发将使用手动锁定来处理,但人们普遍认为,在大多数情况下,手动锁定是管理并发的糟糕方法。当我们考虑到专家建议我们更多使用的并发原语时,讨论会发生什么变化,例如软件事务内存,或者避免共享内存以支持消息传递(可能使用同步,如在 go 中)?
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 有帮助的两个最佳改进是
现在我们有了 MVVM (Model-View-ViewModel) 模式,这是一个额外的数据重复。当开发工具包时,即使是 MVC 中的单个重复也引起了激烈的争论。 MVVM 使多线程更容易。恕我直言,这是 Microsoft 首先在 .NET 中发明它的主要原因,而不是数据绑定。
场景图方法。 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 是单线程的说法不再正确。