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

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

问题描述

之前提出过严重相关的问题:






  • 第一个问题的asker询问多线程是否会帮助性能,回答者大多表示不会,因为GUI不太可能是现代硬件上2D应用程序的瓶颈。但在我看来这是一个鬼祟的辩论策略。当然,如果你已经仔细结构化你的应用程序做任何事情,除了UI线程上的UI调用,你不会有瓶颈。但这可能需要大量的工作,使你的代码更复杂,如果你有一个更快的核心或可以从多个线程的UI调用,也许这不值得做。


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


  • 使用UI控件来确定情况,但是它们的回调只能由系统调用,为什么它们会导致任何特殊的死锁问题?毕竟,如果回调需要花费任何时间,他们将委托给另一个线程,然后我们回到多线程的情况。


  • 如果你可以在UI线程上阻塞,多线程UI的好处是多少?因为在async中的各种新兴的抽象效果让你这样做。


  • 几乎所有的讨论都假定并发将使用手动锁定,但是有一个广泛的共识,手动锁是在大多数上下文中管理并发性的坏方法。当我们考虑专家建议我们使用更多的并发原语(例如软件事务内存)或者使用共享内存(有利于消息传递)(可能同步,如go)时,讨论如何改变? p>



解决方案

TL; DR



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



讨论



在系统中处理具有单一身份的长期资源通常是用单个线程,进程,对象或任何代表关于给定语言中的并发性的原子单位的代表它们。回到非空,忽略内核,非分时,一个真正的线程天,这是通过轮询/循环或编写自己的调度系统手动管理。在这样的系统中,你仍然有一个1 :: 1映射函数/对象/东西和单一资源(或你在8年级之前疯了)。



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



例如,在聊天程序你通常不会写单线程。你会有一个GUI线程,一个网络线程,和一些其他线程处理日志资源或任何。对于一个典型的系统来说,这是很常见的,它更容易把日志和输入放在同一线程,使GUI更新,但并不总是这样。然而,在所有情况下,每个类别的资源最容易推理通过授予他们单个线程,这意味着一个线程为网络,一个线程为GUI,然而许多其他线程是长寿命操作或



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



因此GUI框架不是多线程本身,它是GUI循环,需要一个单线程来完全管理那个单一的长期资源。编程示例通常本质上是微不足道的,通常是单线程的,所有的程序逻辑运行在与GUI循环相同的进程/线程中,但这在更复杂的程序中并不常见。



总结



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



在简单的程序中,将其他程序逻辑放入GUI线程,但是当经历大量负载或资源管理需要大量阻塞或轮询时,此方法会崩溃,这就是为什么您将经常看到事件队列,信号槽消息抽象或其他多线程/处理方法几乎任何GUI库的灰尘角落(这里我包括游戏库 - 而游戏库通常希望你想要基本上围绕你自己的UI概念构建自己的小部件,基本原则非常相似,只是一点点-level)。



[另外,我最近做了很多Qt / C ++和Wx / Erlang。 Qt文档很好地解释了多线程的方法, GUI环路的作用,以及Qt的信号/时隙方法适用于抽象(因此您不必非常考虑并发/锁定/排序/调度等)。 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天全站免登陆