线程安全和不可变关系
thread safety and immutable relation
不可变对象是线程安全的,这是一个常见的概念。每个有经验的 java 开发人员(或任何其他 oop 开发人员)都知道这个事实,但是当谈到 为什么 的问题时,许多开发人员说 mmmm oooooo我想等等。我想我是那些开发者中的一员。
线程是有目的的东西。其中之一是改变某物的状态。如果你的话题连一件事都没有改变,你为什么要 运行 这样的话题?
我真的很想看到一个真实的例子让我说 "oo i must really use an immutable object to accomplish thread-safety here"
线程可以改变共享对象的状态,但不一定要改变它们有权访问的所有对象。通常是要处理的输入数据或更改代码流的对象。例如配置通常是不可变的,以防止可能导致混乱的不一致状态的并发修改。
我认为不变性更多地与编程中的一般安全感有关,而不是线程安全性(包括避免死锁等)。
不变性的概念是指保证。
当不可变对象在同一线程中的线程或不同模块之间共享时 运行,线程或模块中共享对象的有意或无意突变的负面影响不存在。因此它是安全的。可以保证对象无论位于何处都保持相同的状态。
I really want to see a real-life example that makes me say "oo i must
really use an immutable object to accomplish thread-safety here"
我从未发现不变性对于多线程至关重要,但另一方面,我在职业生涯中遇到的所有竞争条件和死锁都与可变共享状态有关。后见之明比先见之明更容易理解不变性的好处。
也就是说,很难看出不变性如何解决这些场景中的问题,因为在其中许多场景中,它没有解决眼前的绘图板问题。有时您只是遇到两个或多个线程需要访问共享状态的情况,其中它们都需要查看该共享状态的最新版本。不变性并不能解决那里的任何问题。但是,它可以使错误导致的错误的灾难性降低得多。而不是一些模糊的竞争条件,这种条件只在满月时在万分之一的用户机器上发生一次,你至少可以得到一个错误,在那些概念性场景中使用不可变对象 detect/reproduce 更容易 detect/reproduce 两个一个或多个线程需要以某种方式共享状态,使双方都能看到共享数据的最多 up-to-date 个版本。
个人例子
然而,就像我最近发现不可变数据结构,或更具体地说持久数据结构,对多线程非常有用的例子一样:
... 是渲染和动画线程,不需要 访问最新的 "mutation"。在上面的例子中(几年前在 i3 上的原型有点旧,但我想避免在这个网站上展示我的商业作品以避免过热),我使用了我创建的持久网格数据结构这是不可变的。当用户刷过网格(超过 400 万个四边形)时的每一帧,都会创建一个新网格(不能修改原始网格,因为它是不可变的)。但是,新网格避免了深度复制未修改的数据。
Threads are things that has a purpose. One of them is to change a
state of a something. If your thread isn't changing even one thing why
would you run a thread like that?
我发现不可变网格数据结构对多线程有直接帮助的地方是渲染和动画线程。两者都不需要看到最新版本的场景。只要他们提供交互式反馈,他们就可以稍微落后于用户的更改。他们不需要修改 "scene"。他们只需要将一些东西输出到屏幕,而不是场景,所以他们 read-only 相对于他们的输入和输出到其他地方,如果他们不与其他人完美同步也没关系 copies/references/pointers 到源数据。
因此,使用这个不可变的网格数据结构,我能够让渲染线程每帧复制整个场景,然后开始渲染它,而其他线程可以自由创建新的更改网格化所有他们喜欢的。如果没有这种不可变的网格结构,我可能不得不将渲染线程与执行雕刻和更改网格的线程放在同一个线程中,或者我将不得不认真优化它以仅深度复制相关部分网格(以及整个场景的其余部分)以尽可能快地渲染,或者甚至可能做一些复杂的事情并尝试将渲染数据同步到网格数据并且只在单个线程中选择性地更新其中的一部分(在一个在渲染线程开始工作之前锁定)。
有了不可变的网格数据结构,渲染线程要做的就是:
in rendering thread:
on scene change:
copy entire scene // this is dirt cheap
render scene
即使有数百万个多边形、顶点位置、边缘数据和纹理坐标,上面那个不可变网格的副本占用不到 1 千字节(原始占用超过 40 兆字节,尽管使用了压缩索引,16 -bit half-floats, 等等),并且在线程不需要保持完美同步的情况下,你倾向于使用大量不可变数据结构获得的超级便宜的复制对于多线程来说真的很方便(不需要查看共享数据的最多 up-to-date 个版本),它也可以方便地用于撤消系统、non-destructive 编辑、实例化、exception-safety 等
这一切都围绕着一个不可变的数组概念,其工作方式如下:
约翰·卡马克
我是一个用 C 语言构建不可变数据结构的疯子,但自从我听到 John Carmack 的演讲后我受到了启发,他似乎相信你可以创建围绕不可变数据结构和纯函数的视频游戏编程。我曾经认为不变性所涉及的开销(需要创建新的东西并分配内存而不是修改原始内存)将是非常巨大的,但是如果约翰卡马克(我的某种偶像,我们都来自同一个编程generation) 可以设想一个围绕这种数据结构构建的 AAA 视频游戏,我想我不妨试一试,因为对他来说足够好的东西对我来说也应该绰绰有余。 VFX 在 FPS 方面的要求远不及 AAA 游戏-- 如果他们的内容可以达到 30+ FPS,艺术家通常会很高兴(尽管内容通常比游戏引擎必须处理的内容更普遍和复杂)。
从那时起,我一直在探索我当前领域(电影和电视的视觉效果等)中的这些想法,但我无法像以前使用的可变数据结构那样快速获得变异结果(虽然我不是 John Carmack),但我可以相当接近,而且作为奖励,现在我可以更轻松地创建更快的渲染器、动画师等(线程可能有点滞后)。它简化了很多东西(虽然最大的简化实际上不是多线程,而是 non-destructive 编辑和实例化)。简化是OMG。我真的不能夸大它。它确实在许多地方的某些情况下将数万行代码变成了一行代码*。不变性可能会让你反思自己的职业生涯,想知道为什么你不考虑早点使用它,也会改变你反思职业生涯中过去的设计决策的方式,当你可能不会否则。这是来自一个仍然认为垃圾收集很糟糕的极端偏见和固执的人。
In fairness it wasn't exactly trivial to implement these persistent data structures, but the amount of time they took and the code required was far, far exceeded by the amount of time and code they reduced. The benefits far, far exceeded the costs, so to speak, at least in my case because you have this one moderately-complex persistent, thread-safe data structure which is, in exchange, tremendously simplifying over a hundred different places in the system which use it.
廉价抄袭
通常在这些类型的上下文中,您拥有这些线程,其唯一目的是将某些内容输出到屏幕,并涉及大量中间处理以将这些像素传递到屏幕,而用户使用的可能是某些东西否则(副本)。如果这两个以上的副本彼此稍微不同步也没关系,只要以用户无法分辨差异的方式足够快地交付帧即可。因此,您可以让用户使用一个副本,而其他线程则复制周围的内容并开始将像素传递到屏幕。在我的案例中,不可变数据结构真正有用的地方在于它们使复制变得便宜。如果超过 40 兆字节的数据(在 VFX 中,数据有时可能跨越千兆字节)必须在用户触摸任何东西的每一帧中始终从一个线程到另一个线程完整复制,帧速率将开始爬行。使用不可变(特别是持久性)数据结构,复制变得非常便宜,我们最终只需要复制千字节的数据,甚至复制史诗般的场景。这足以提供所需的 60+ FPS。
作为我最初探索不变性概念的另一件事,我制作了一个粒子演示(也有 400 万个粒子:不知何故我喜欢 400 万)。这里对粒子使用不可变数据结构不一定那么有益,但我只是想要一些非常直观的东西来评估它的性能:
每帧都会创建一个新的粒子集合。我能够使用不可变的持久数据结构在 i3 上以超过 180 FPS 的速度完成此操作。同一演示的可变版本 运行 超过 300+ FPS,但请记住,粒子模拟非常简单(每个粒子的处理都很简单,如果应用更复杂的逻辑,差异就不会如此倾斜每个粒子)。我不确定使数据结构不可变的存储粒子在长期 运行 中是否会有好处(无法立即想到有很大帮助的情况),但它主要是一个视觉基准,因为我最初来自 80 年代和 90 年代*的游戏开发背景,我对 "acceptable performance" 的想法与帧速率相关,具有非常视觉化的思维方式。所以我喜欢以这种方式在视觉上对事物进行基准测试。为看起来像垃圾的 GIF 道歉。我很难对它们进行编码,不得不大幅降低它们的帧速率。明显的 stutters/lags 在原文中也不存在。
- Actually John Carmack might have put me out of work in gamedev with
the advent of 3D games which are so much more expensive to create
which put me out of a job as an indie gamedev in the early-mid 90s and moved me to the VFX
industry working in films and television and archviz and only content creation for games, but I idolize him anyway -- he always seemed ten steps ahead
of me at all given points in time for the past few decades and I'm like a tortoise trying to catch up to him, always half a decade or so behind his thinking. Now he's all over the immutability and functional programming bandwagon, and I don't think he has turned soft at all. I'm willing to gamble that he's on to something really important, at least for gamedev.
不可变对象是线程安全的,这是一个常见的概念。每个有经验的 java 开发人员(或任何其他 oop 开发人员)都知道这个事实,但是当谈到 为什么 的问题时,许多开发人员说 mmmm oooooo我想等等。我想我是那些开发者中的一员。
线程是有目的的东西。其中之一是改变某物的状态。如果你的话题连一件事都没有改变,你为什么要 运行 这样的话题?
我真的很想看到一个真实的例子让我说 "oo i must really use an immutable object to accomplish thread-safety here"
线程可以改变共享对象的状态,但不一定要改变它们有权访问的所有对象。通常是要处理的输入数据或更改代码流的对象。例如配置通常是不可变的,以防止可能导致混乱的不一致状态的并发修改。
我认为不变性更多地与编程中的一般安全感有关,而不是线程安全性(包括避免死锁等)。
不变性的概念是指保证。
当不可变对象在同一线程中的线程或不同模块之间共享时 运行,线程或模块中共享对象的有意或无意突变的负面影响不存在。因此它是安全的。可以保证对象无论位于何处都保持相同的状态。
I really want to see a real-life example that makes me say "oo i must really use an immutable object to accomplish thread-safety here"
我从未发现不变性对于多线程至关重要,但另一方面,我在职业生涯中遇到的所有竞争条件和死锁都与可变共享状态有关。后见之明比先见之明更容易理解不变性的好处。
也就是说,很难看出不变性如何解决这些场景中的问题,因为在其中许多场景中,它没有解决眼前的绘图板问题。有时您只是遇到两个或多个线程需要访问共享状态的情况,其中它们都需要查看该共享状态的最新版本。不变性并不能解决那里的任何问题。但是,它可以使错误导致的错误的灾难性降低得多。而不是一些模糊的竞争条件,这种条件只在满月时在万分之一的用户机器上发生一次,你至少可以得到一个错误,在那些概念性场景中使用不可变对象 detect/reproduce 更容易 detect/reproduce 两个一个或多个线程需要以某种方式共享状态,使双方都能看到共享数据的最多 up-to-date 个版本。
个人例子
然而,就像我最近发现不可变数据结构,或更具体地说持久数据结构,对多线程非常有用的例子一样:
... 是渲染和动画线程,不需要 访问最新的 "mutation"。在上面的例子中(几年前在 i3 上的原型有点旧,但我想避免在这个网站上展示我的商业作品以避免过热),我使用了我创建的持久网格数据结构这是不可变的。当用户刷过网格(超过 400 万个四边形)时的每一帧,都会创建一个新网格(不能修改原始网格,因为它是不可变的)。但是,新网格避免了深度复制未修改的数据。
Threads are things that has a purpose. One of them is to change a state of a something. If your thread isn't changing even one thing why would you run a thread like that?
我发现不可变网格数据结构对多线程有直接帮助的地方是渲染和动画线程。两者都不需要看到最新版本的场景。只要他们提供交互式反馈,他们就可以稍微落后于用户的更改。他们不需要修改 "scene"。他们只需要将一些东西输出到屏幕,而不是场景,所以他们 read-only 相对于他们的输入和输出到其他地方,如果他们不与其他人完美同步也没关系 copies/references/pointers 到源数据。
因此,使用这个不可变的网格数据结构,我能够让渲染线程每帧复制整个场景,然后开始渲染它,而其他线程可以自由创建新的更改网格化所有他们喜欢的。如果没有这种不可变的网格结构,我可能不得不将渲染线程与执行雕刻和更改网格的线程放在同一个线程中,或者我将不得不认真优化它以仅深度复制相关部分网格(以及整个场景的其余部分)以尽可能快地渲染,或者甚至可能做一些复杂的事情并尝试将渲染数据同步到网格数据并且只在单个线程中选择性地更新其中的一部分(在一个在渲染线程开始工作之前锁定)。
有了不可变的网格数据结构,渲染线程要做的就是:
in rendering thread:
on scene change:
copy entire scene // this is dirt cheap
render scene
即使有数百万个多边形、顶点位置、边缘数据和纹理坐标,上面那个不可变网格的副本占用不到 1 千字节(原始占用超过 40 兆字节,尽管使用了压缩索引,16 -bit half-floats, 等等),并且在线程不需要保持完美同步的情况下,你倾向于使用大量不可变数据结构获得的超级便宜的复制对于多线程来说真的很方便(不需要查看共享数据的最多 up-to-date 个版本),它也可以方便地用于撤消系统、non-destructive 编辑、实例化、exception-safety 等
这一切都围绕着一个不可变的数组概念,其工作方式如下:
约翰·卡马克
我是一个用 C 语言构建不可变数据结构的疯子,但自从我听到 John Carmack 的演讲后我受到了启发,他似乎相信你可以创建围绕不可变数据结构和纯函数的视频游戏编程。我曾经认为不变性所涉及的开销(需要创建新的东西并分配内存而不是修改原始内存)将是非常巨大的,但是如果约翰卡马克(我的某种偶像,我们都来自同一个编程generation) 可以设想一个围绕这种数据结构构建的 AAA 视频游戏,我想我不妨试一试,因为对他来说足够好的东西对我来说也应该绰绰有余。 VFX 在 FPS 方面的要求远不及 AAA 游戏-- 如果他们的内容可以达到 30+ FPS,艺术家通常会很高兴(尽管内容通常比游戏引擎必须处理的内容更普遍和复杂)。
从那时起,我一直在探索我当前领域(电影和电视的视觉效果等)中的这些想法,但我无法像以前使用的可变数据结构那样快速获得变异结果(虽然我不是 John Carmack),但我可以相当接近,而且作为奖励,现在我可以更轻松地创建更快的渲染器、动画师等(线程可能有点滞后)。它简化了很多东西(虽然最大的简化实际上不是多线程,而是 non-destructive 编辑和实例化)。简化是OMG。我真的不能夸大它。它确实在许多地方的某些情况下将数万行代码变成了一行代码*。不变性可能会让你反思自己的职业生涯,想知道为什么你不考虑早点使用它,也会改变你反思职业生涯中过去的设计决策的方式,当你可能不会否则。这是来自一个仍然认为垃圾收集很糟糕的极端偏见和固执的人。
In fairness it wasn't exactly trivial to implement these persistent data structures, but the amount of time they took and the code required was far, far exceeded by the amount of time and code they reduced. The benefits far, far exceeded the costs, so to speak, at least in my case because you have this one moderately-complex persistent, thread-safe data structure which is, in exchange, tremendously simplifying over a hundred different places in the system which use it.
廉价抄袭
通常在这些类型的上下文中,您拥有这些线程,其唯一目的是将某些内容输出到屏幕,并涉及大量中间处理以将这些像素传递到屏幕,而用户使用的可能是某些东西否则(副本)。如果这两个以上的副本彼此稍微不同步也没关系,只要以用户无法分辨差异的方式足够快地交付帧即可。因此,您可以让用户使用一个副本,而其他线程则复制周围的内容并开始将像素传递到屏幕。在我的案例中,不可变数据结构真正有用的地方在于它们使复制变得便宜。如果超过 40 兆字节的数据(在 VFX 中,数据有时可能跨越千兆字节)必须在用户触摸任何东西的每一帧中始终从一个线程到另一个线程完整复制,帧速率将开始爬行。使用不可变(特别是持久性)数据结构,复制变得非常便宜,我们最终只需要复制千字节的数据,甚至复制史诗般的场景。这足以提供所需的 60+ FPS。
作为我最初探索不变性概念的另一件事,我制作了一个粒子演示(也有 400 万个粒子:不知何故我喜欢 400 万)。这里对粒子使用不可变数据结构不一定那么有益,但我只是想要一些非常直观的东西来评估它的性能:
每帧都会创建一个新的粒子集合。我能够使用不可变的持久数据结构在 i3 上以超过 180 FPS 的速度完成此操作。同一演示的可变版本 运行 超过 300+ FPS,但请记住,粒子模拟非常简单(每个粒子的处理都很简单,如果应用更复杂的逻辑,差异就不会如此倾斜每个粒子)。我不确定使数据结构不可变的存储粒子在长期 运行 中是否会有好处(无法立即想到有很大帮助的情况),但它主要是一个视觉基准,因为我最初来自 80 年代和 90 年代*的游戏开发背景,我对 "acceptable performance" 的想法与帧速率相关,具有非常视觉化的思维方式。所以我喜欢以这种方式在视觉上对事物进行基准测试。为看起来像垃圾的 GIF 道歉。我很难对它们进行编码,不得不大幅降低它们的帧速率。明显的 stutters/lags 在原文中也不存在。
- Actually John Carmack might have put me out of work in gamedev with the advent of 3D games which are so much more expensive to create which put me out of a job as an indie gamedev in the early-mid 90s and moved me to the VFX industry working in films and television and archviz and only content creation for games, but I idolize him anyway -- he always seemed ten steps ahead of me at all given points in time for the past few decades and I'm like a tortoise trying to catch up to him, always half a decade or so behind his thinking. Now he's all over the immutability and functional programming bandwagon, and I don't think he has turned soft at all. I'm willing to gamble that he's on to something really important, at least for gamedev.