提问人:super 提问时间:7/25/2021 最后编辑:super 更新时间:7/28/2021 访问量:504
在后台线程上执行 UI
Doing UI on a background thread
问:
线程的 SDL 文档指出:
注意:您不应期望能够在主线程以外的任何线程上创建窗口、呈现或接收事件。
glfwCreateWindow 的 glfw 文档指出:
线程安全:此函数只能从主线程调用。
我从尝试在第二个线程上运行窗口函数的人那里读到了有关 glut 库的问题。
我可以继续举这些例子,但我想你明白我想要表达的观点。许多跨平台库不允许在后台线程上创建窗口。
现在,我提到的两个库在设计时考虑了 OpenGL,我知道 OpenGL 不是为多线程设计的,你不应该在多个线程上进行渲染。没关系。我不明白的是,为什么渲染线程(执行所有渲染的单个线程)必须是应用程序的主要线程。
据我所知,Windows、Linux 和 MacOS 都没有对哪些线程可以创建窗口施加任何限制。我确实知道窗口与创建它们的线程有亲和力(只有该线程可以接收它们的输入等);但是,该线程仍然不需要成为主要线程。
所以,我有三个问题:
- 为什么这些图书馆会施加这样的限制?是因为有一些晦涩难懂的操作系统要求在主线程上创建所有窗口,因此所有操作系统都必须付出代价吗?(还是我弄错了?
- 为什么我们强加你不应该在后台线程上做 UI?无论如何,线程与窗口有什么关系?将逻辑绑定到特定线程不是一个糟糕的抽象吗?
- 如果这是我们所拥有的并且无法摆脱它,我该如何克服这个限制?我是否创建一个类并将主线程提供给它,以便它可以安排需要在主线程中完成的工作以及可以在后台线程中完成的工作?
ThreadManager
如果有人能对这个话题有所了解,那就太棒了。我看到的所有建议都是在主线程上同时进行输入和 UI。但是,如果没有技术原因,那么这只是一个任意的限制,为什么不可能做其他事情。
PS:请注意,我正在寻找跨平台解决方案。如果找不到,我会坚持在主线程上做UI。
答:
这些框架中的任何一个都不太可能真正关心哪个线程是“主线程”,即调用代码入口点的线程。真正的限制是,您必须在初始化框架的线程上完成所有 UI 工作,即在您的情况下调用 SDL_Init 的线程。您通常会在主线程中执行此操作。为什么不呢?
多线程代码很难编写,也很难理解,在UI工作中,引入多线程使得很难推理事情何时发生。UI 是一个非常有状态的东西,当你编写 UI 代码时,你通常需要对已经发生的事情和接下来会发生什么有一个非常好的想法——当涉及多线程时,这些事情通常是未定义的。此外,用户速度很慢,因此在正常情况下,多线程 UI 对于性能来说并不是真正必要的。正因为如此,使 UI 框架线程安全通常不被认为是有益的。(渲染管线的多线程计算密集型部分是另一回事)
单线程 UI 框架具有某种类型的调度程序,可用于将下次有时间时应在主线程上发生的活动排入队列。在 SDL 中,为此使用 SDL_PushEvent。您可以从任何线程调用它。
评论
虽然我对最新版本的 MacOS/iOS 不太了解,但截至 2020 年,Apple UIKit 和 AppKit 不是线程安全的。只有一个线程可以安全地更改 UI 对象,除非你遇到很多麻烦,否则这将是主线程。即使你确实不厌其烦地关闭窗口管理器连接等,你仍然会得到一个线程只做UI。因此,该限制仍然适用于至少一个主要系统。
虽然从任何其他线程直接修改窗口的内容可能不安全,但您可以从任何您喜欢的线程对屏幕外位图图像进行软件渲染,只要您愿意。然后将完成的图像交给主线程进行渲染。(这可能就是为什么跨平台工具包不允许/告诉你不要这样做的原因。有时它可能会起作用,但你不能说为什么,甚至不能说它会继续起作用。
使用 Vulkan 和 DirectX 12(我认为但不确定 Metal),您可以从多个线程进行渲染。呜呼!当然,现在你必须弄清楚如何完成所有的协调、锁定和交叉同步,而不会使整个事情比单线程慢,但至少你可以选择尝试。
除了Matt的出色回答之外,您还可以使用Qt程序,并让后台线程安全地更新UI。invokeMethod
postEvent
评论