为什么 UI 框架必须/应该是单线程的? [英] Why must/should UI frameworks be single threaded?

查看:22
本文介绍了为什么 UI 框架必须/应该是单线程的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Closely related questions have been asked before:

但这些问题的答案仍然让我在某些方面不清楚.

But the answers to those questions still leave me unclear on some points.

  • 第一个问题的提问者问多线程是否有助于性能,回答者大多说不会,因为 GUI 不太可能成为现代硬件上 2D 应用程序的瓶颈.但这在我看来是一种偷偷摸摸的辩论策略.当然,如果您仔细构建了您的应用程序,除了在 UI 线程上调用 UI 之外什么都不做,那么您将不会遇到瓶颈.但这可能需要大量工作并使您的代码更加复杂,如果您有一个更快的内核或者可以从多个线程进行 UI 调用,那么可能就不值得这样做了.

  • The asker of the first question asked if multi-threading would help performance, and the answerers mostly said that it would not, because it is very unlikely that the GUI would be the bottleneck in a 2D application on modern hardware. But this seems to me a sneaky debating tactic. Sure, if you have carefully structured your application to do nothing other than UI calls on the UI thread you won't have a bottleneck. But that might take a lot of work and make your code more complicated, and if you had a faster core or could make UI calls from multiple threads, maybe it wouldn't be worth doing.

一个普遍提倡的架构设计是让视图组件没有回调并且不需要锁定任何东西,除了它们的后代.在这样的架构下,难道不能让任何线程调用视图对象的方法,使用逐对象锁,而不用担心死锁吗?

A commonly advocated architectural design is to have view components that don't have callbacks and don't need to lock anything except maybe their descendants. Under such an architecture, can't you let any thread invoke methods on view objects, using per-object locks, without fear of deadlock?

我对 UI 控件的情况不太自信,但只要它们的回调仅由系统调用,为什么它们会导致任何特殊死锁问题?毕竟,如果回调需要做一些耗时的事情,它们会委托给另一个线程,然后我们又回到了多线程的情况.

I am less confident about the situation with UI controls, but as long their callbacks are only invoked by the system, why should they cause any special deadlock issues? After all, if the callbacks need to do anything time consuming, they will delegate to another thread, and then we're right back in the multiple threads case.

如果您可以在 UI 线程上阻塞,您会获得多线程 UI 的多少好处?因为异步上的各种新兴抽象实际上可以让您做到这一点.

How much of the benefit of a multi-threaded UI would you get if you could just block on the UI thread? Because various emerging abstractions over async in effect let you do that.

我所看到的几乎所有讨论都假设并发将使用手动锁定来处理,但有一个广泛的共识,即手动锁定在大多数情况下是一种糟糕的并发管理方式.当我们考虑到专家建议我们更多使用的并发原语时,讨论会如何变化,例如软件事务内存,或避免共享内存以支持消息传递(可能与同步,如 go)?

Almost all of the discussion I have seen assumes that concurrency will be dealt with using manual locking, but there is a broad consensus that manual locking is a bad way to manage concurrency in most contexts. How does the discussion change when we take into consideration the concurrency primitives that the experts are advising us to use more, such as software transactional memory, or eschewing shared memory in favor of message passing (possibly with synchronization, as in go)?

推荐答案

TL;DR

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

It is a simple way to force sequencing to occur in an activity that is going to ultimately be in sequence anyway (the screen draw X times per second, in order).

讨论

处理在系统内具有单一身份的长期持有资源通常是通过用单个线程、进程、对象"或任何其他表示与给定语言中的并发性相关的原子单元来表示它们来完成的.回到非空的、疏忽的内核、非分时、单线程时代,这是通过轮询/循环或编写自己的调度系统手动管理的.在这样的系统中,您仍然有函数/对象/事物和单一资源之间的 1::1 映射(或者您在 8 年级之前就发疯了).

Handling long-held resources which have a single identity within a system is typically done by representing them with a single thread, process, "object" or whatever else represents an atomic unit with regard to concurrency in a given language. Back in the non-emptive, negligent-kernel, non-timeshared, One True Thread days this was managed manually by polling/cycling or writing your own scheduling system. In such a system you still had a 1::1 mapping between function/object/thingy and singular resources (or you went mad before 8th grade).

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

This is the same approach used with handling network sockets, or any other long-lived resource. The GUI itself is but one of many such resources a typical program manages, and typically long-lived resources are places where the ordering of events matters.

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

For example, in a chat program you would usually not write a single thread. You would have a GUI thread, a network thread, and maybe some other thread that deals with logging resources or whatever. It is not uncommon for a typical system to be so fast that its easier to just put the logging and input into the same thread that makes GUI updates, but this is not always the case. In all cases, though, each category of resources is most easily reasoned about by granting them a single thread, and that means one thread for the network, one thread for the GUI, and however many other threads are necessary for long-lived operations or resources to be managed without blocking the others.

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

To make life easier its common to not share data directly among these threads as much as possible. Queues are much easier to reason about than resource locks and can guarantee sequencing. Most GUI libraries either queue events to be handled (so they can be evaluated in order) or commit data changes required by events immediately, but get a lock on the state of the GUI prior to each pass of the repaint loop. It doesn't matter what happened before, the only thing that matters when painting the screen is the state of the world right then. This is slightly different than the typical network case where all the data needs to be sent in order and forgetting about some of it is not an option.

因此,GUI 框架本身并不是多线程的,GUI 循环需要成为单个线程才能理智地管理单个长期持有的资源.编程示例通常本质上是微不足道的,通常是单线程的,所有程序逻辑都在与 GUI 循环相同的进程/线程中运行,但这在更复杂的程序中并不常见.

So GUI frameworks are not multi-threaded, per se, it is the GUI loop that needs to be a single thread to sanely manage that single long-held resource. Programming examples, typically being trivial by nature, are often single-threaded with all the program logic running in the same process/thread as the GUI loop, but this is not typical in more complex programs.

总结

因为调度难,共享数据管理更难,单个资源无论如何只能串行访问,所以用一个线程来表示每个长期持有的资源和每个长期运行的过程是典型的代码结构方式.GUI 只是典型程序将管理的多种资源中的一种.因此,GUI 程序"绝不是单线程的,但 GUI 库通常是.

Because scheduling is hard, shared data management is even harder, and a single resource can only be accessed serially anyway, a single thread used to represent each long-held resource and each long-running procedure is a typical way to structure code. GUIs are only one resource among several that a typical program will manage. So "GUI programs" are by no means single-threaded, but GUI libraries typically are.

在琐碎的程序中,将其他程序逻辑放在 GUI 线程中并没有实现惩罚,但是当遇到大量负载或资源管理需要大量阻塞或轮询时,这种方法就会失效,这就是您经常看到的原因事件队列、信号槽消息抽象或其他多线程/处理方法在几乎所有 GUI 库的尘土飞扬的角落中提到(在这里我包括游戏库——而游戏库通常希望你想要基本上构建你的自己的widgets围绕自己的UI概念,基本原理非常相似,只是低了一点).

In trivial programs there is no realized penalty to putting other program logic in the GUI thread, but this approach falls apart when significant loads are experienced or resource management requires either a lot of blocking or polling, which is why you will often see event queue, signal-slot message abstractions or other approaches to multi-threading/processing mentioned in the dusty corners of nearly any GUI library (and here I'm including game libraries -- while game libs typically expect that you want to essentially build your own widgets around your own UI concept, the basic principles are very similar, just a bit lower-level).

[顺便说一句,我最近做了很多 Qt/C++ 和 Wx/Erlang.Qt 文档很好地解释了多线程方法,GUI 循环的作用,以及 Qt 的信号/槽方法适合抽象的地方(因此您不必非常考虑并发/锁定/排序/调度/等).Erlang 本质上是并发的,但是 wx 本身通常作为管理 GUI 更新的单个操作系统进程启动loop 和 Erlang 将更新事件作为消息发送给它,GUI 事件作为消息发送到 Erlang 端——因此允许正常的 Erlang 并发编码,但提供了单点 GUI 事件排序,以便 wx 可以执行其GUI更新循环的东西.]

[As an aside, I've been doing a lot of Qt/C++ and Wx/Erlang lately. 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 and Erlang posts update events to it as messages, and GUI events are sent to the Erlang side as messages -- thus permitting normal Erlang concurrent coding, but providing a single point of GUI event sequencing so that wx can do its GUI update looping thing.]

这篇关于为什么 UI 框架必须/应该是单线程的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆