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

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

问题描述

以前已经提出过紧密相关的问题:





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




  • 第一个问题的提问者询问多线程是否有助于性能,答复者大多表示不会,因为GUI不太可能成为现代硬件上的2D应用程序的瓶颈。但这似乎是一个鬼祟的辩论策略。当然,如果您在UI线程上仔细地将应用程序结构化为除UI调用之外的其他任何操作,您将不会有瓶颈。但是,这可能需要大量的工作,使您的代码变得更加复杂,如果您有更快的内核或可以从多个线程进行UI调用,也许不值得一提。

  • $ b通常所倡导的架构设计是具有不具有回调的视图组件,并且不需要锁定除了后代之外的任何东西。在这样的架构下,你不能让任何线程调用视图对象上的方法,使用每个对象的锁,而不用担心死锁?


  • 我不太对UI控件的情况有所了解,但是只要系统调用回调,为什么会导致任何特殊死锁问题?毕竟,如果回调需要做任何耗时的事情,他们将委托给另一个线程,然后我们回到多线程情况。


  • 如果您只能阻止UI线程,您将获得多线程UI的好处多少?因为各种不同的新兴抽象在异步实际上让你这样做。


  • 几乎我所看到的所有讨论都假设并发处理使用手动锁定,但是有一个广泛的共识是,手动锁定是在大多数情况下管理并发的坏方法。当我们考虑到专家建议我们使用更多的并发原语(例如软件事务内存)或者避开共享内存以支持消息传递(可能同步,如同样)时,讨论如何变化? p>



解决方案

TL; DR



这是一种简单的方法来强制排序发生在一个最终将顺序进行的活动中(屏幕每秒按顺序绘制X次)。



讨论



处理在系统中具有单个身份的长期资源通常由用一个线程,进程,对象或其他代表给定语言的并发性的原子单位来表示它们。通过轮询/骑自行车或编写自己的调度系统手动管理的非真实的,疏忽的内核,非时间共享的One True Thread天。在这样一个系统中,你仍然在函数/对象/事物和奇异资源之间进行了1 :: 1的映射(或者你在8年级以前发疯)。



与处理网络套接字或任何其他长寿命资源相同的方法。 GUI本身只是一个典型的程序管理的许多这样的资源之一,通常长期的资源是事件顺序重要的地方。



例如,在聊天程序你通常不会写单线程。你会有一个GUI线程,一个网络线程,也许一些其他线程处理日志资源或其他任何东西。典型的系统变得如此之快,将记录和输入更容易地放入使用GUI更新的同一个线程中并不罕见,但并不总是如此。然而,在所有情况下,每个类别的资源最容易被认为是通过授予它们一个线程,这意味着网络的一个线程,GUI的一个线程,然而许多其他线程对于长期存在的操作是必要的,资源被管理而不会阻止其他人。



为了使生活变得更简单,共同的不是直接在这些线程之间共享数据。队列比资源锁更容易理解,可以保证排序。大多数GUI库可以对要处理的事件进行排队(因此可以按顺序进行评估)或立即提交事件所需的数据更改,但在每次重新生成循环之前都会获取对GUI状态的锁定。以前发生的事情并不重要,画画时唯一重要的是世界的状态,然后。这与典型的网络情况略有不同,所有的数据都需要按顺序发送,并且忘记了一些数据不是一个选项。



所以GUI框架不是多线程本身就是GUI循环,需要一个单一的线程才能正确地管理这个单一的长期资源。编程示例通常是微不足道的,通常是单线程的,所有程序逻辑与GUI循环在同一进程/线程中运行,但这在更复杂的程序中不是典型的。



总结



由于调度困难,共享数据管理更加困难,单一资源只能无论如何连续访问,用于表示每个长期持有的资源的单个线程和每个长期运行的过程是构建代码的典型方式。 GUI只是典型程序将管理的几种资源之一。因此,GUI程序绝不是单线程的,但GUI库通常是。



在微不足道的程序中,将其他程序逻辑放在GUI中没有意识到的惩罚线程,但是这种方法在经历重要负载或资源管理需要大量阻塞或轮询时会崩溃,这就是为什么您经常会看到事件队列,信号时隙消息抽象或其他提到的多线程/处理方法几乎任何GUI库的灰尘的角落(这里我包括游戏库 - 而游戏lib通常期望您想要在您自己的UI概念周围建立自己的小部件,基本原理非常相似,只是稍微降低一点-level)。



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


Closely related questions have been asked before:

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

  • 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?

  • 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.

  • 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.

  • 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

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).

Discussion

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).

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.

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.

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.

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.

To sum up

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.

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).

[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天全站免登陆