线程安全性和不可变关系

时间:2017-11-08 22:36:21

标签: java oop thread-safety immutability

不可变对象是线程安全的常见概念。每个有经验的java开发人员(或任何其他oop开发人员)都知道这个事实,但是当谈到 为什么 时,许多开发人员都会问mmmm ooooo我认为等等。我认为我是其中之一那些开发者。

线程是有目的的东西。其中之一就是改变某种状态。如果你的线程没有改变,那么为什么你会运行这样的线程?

我真的希望看到一个真实的例子让我说“我必须真正使用不可变对象来实现线程安全”

3 个答案:

答案 0 :(得分:1)

  

我真的很想看到一个真实的例子让我说“我必须”   真的使用不可变对象在这里完成线程安全"

我从来没有发现不可变性对于多线程是必不可少的,但另一方面,我在职业生涯中遇到的所有竞争条件和僵局都与可变共享状态有关。在后见之明,比在远见中更容易理解不变性的好处。

也就是说,很难看出不变性如何解决这些场景的问题,因为在很多场景中,并没有解决眼前的绘图板问题。有时您只有两个或多个线程需要访问共享状态的情况,其中两个线程都需要查看该共享状态的最新版本。不变性并不能解决任何问题。然而,它可以使错误导致的错误远远不那么灾难性。而不是仅仅出现在万分之一用户中的一些模糊的竞争条件。机器一旦满月,你可能至少能够在两个或多个线程需要以两种方式共享状态的概念场景中使用不可变的方法来获取更容易检测/再现的错误最新版本的共享数据。

个人示例

然而,正如最近我发现不可变数据结构,或更具体的持久数据结构的例子,对多线程非常有用:

enter image description here

...是渲染和的动画线程需要访问最新的"变种"。在上面的示例中(几年前在i3上的原型中有点老了,但我想避免在这个网站上展示我的商业工作以避免热量),我使用了持久性我创建的网格数据结构是不可变的。当用户刷过网格(超过400万个四边形)时,每个帧都会创建一个新网格(原始网格不能被修改,因为它是不可变的)。但是,新网格避免了深度复制未修改的数据。

  

线程是有目的的东西。其中一个是改变一个   某事物的状态。如果你的线程没有改变甚至一件事   你会运行这样的线程吗?

我发现立即有助于多线程的不可变网格数据结构是渲染和动画线程。两者都不需要看到最新版本的场景。只要用户提供交互式反馈,他们就可以稍微落后于用户的变化。他们不需要修改"场景"。他们只需要输出一些东西到屏幕上,而不是输出到场景中,所以它们只是输入和输出到其他地方是只读的,如果它们不能与其他地方完美同步就可以了。复制/引用/指向源数据。

结果,使用这个不可变的网格数据结构,我能够让渲染线程每个帧复制整个场景,然后开始渲染它,而其他线程可以自由创建新的更改的网格他们喜欢。如果没有这个不可变的网格结构,我可能不得不将渲染线程放在与进行雕刻和更改网格的线程相同的线程中,否则我将不得不认真优化它以仅深度复制相关的部分。网格(以及整个场景的其余部分)用于尽可能快地渲染,或甚至可能做一些精心设计并尝试将渲染数据同步到网格数据,并且只在一个线程中选择性地更新它的一部分(在一个线程内)锁定)在渲染线程开始工作之前。

使用不可变的网格数据结构,渲染线程所要做的就是:

in rendering thread:
    on scene change:
        copy entire scene // this is dirt cheap
        render scene

即使有数百万个多边形,顶点位置,边缘数据和纹理坐标,上面的不可变网格的副本也不到一千字节(原始版本需要超过40兆字节,而且尽管使用了压缩索引,16位半浮点数等),并且在线程不需要保持完美同步的情况下,多线程可以非常方便地使用大量不可变数据结构的超便宜复制(don&# 39;需要查看最新版本的共享数据),它还可以方便用于撤销系统,非破坏性编辑,实例化,异常安全等。

这一切都围绕着一个像这样工作的不可变数组概念:

enter image description here

John Carmack

我是一个在C中构建不可变数据结构的疯狂人物,但自从我听到John Carmack发表讲话后我得到了启发,他似乎相信你可以创建围绕不可变数据结构和纯功能的视频游戏节目。我曾经认为不可变性所涉及的开销(需要创建新东西并分配内存而不是修改原始内存)将是非常巨大的,但如果John Carmack(我的偶像,我们都来自同一个编程)一代人可以想象一个围绕这些数据结构建立的AAA视频游戏,我想我也可以试一试,因为对他来说这对他来说应该足够好。对于FPS而言,VFX远不如AAA游戏那么苛刻 - 艺术家通常很高兴如果他们的内容可以获得30+ FPS(尽管内容通常比游戏引擎必须处理的更加通用和复杂)。

从那时起,我一直在我目前的领域(电影和电视的视觉效果等)探索这些想法,我不能像我可变的数据结构一样快速地获得结果。使用之前(虽然我不是John Carmack),但我可以合理地接近,作为奖励,现在我可以更轻松地创建更快的渲染器,动画师等(线程可以稍微落后一点) 。它已经简化了很多东西(虽然最大的简化实际上并不是多线程,而是非破坏性编辑和实例化)。简化是OMG。我无法夸大它。在某些情况下,它在许多地方*已经成千上万行代码成为一行代码*。不变性可以让你反思自己的职业生涯,并想知道你为什么不考虑更早地使用更多的东西,并且改变你在职业生涯中反思过去的设计决定的方式,当你犯下错误时可能没有。这是来自一个极端偏见和顽固的人,他仍然认为垃圾收集很糟糕。

  

公平地说,实现这些持久性数据结构并不是一件容易的事,但是它们所花费的时间和所需的代码远远超过了它们减少的时间和代码量。好处远远超过了成本,可以这么说,至少在我的情况下,因为你有一个中等复杂的持久性,线程安全的数据结构,作为交换,极大地简化了系统中的一百多个不同的地方。用它。

污垢便宜的复制

通常在这些类型的上下文中,您拥有这些线程,其唯一目的是向屏幕输出内容,并通过大量中间处理将这些像素传递到屏幕,而用户使用的内容可能是其他内容(复印件)。如果这两个+副本彼此略微不同步,只要这些帧以一种用户无法区分的方式足够快地传送,这是可以的。因此,您可以让用户使用副本,而其他线程可以复制内容并开始处理将像素传送到屏幕。在我的情况下,不可变数据结构真正有用的地方在于它们使复制变得便宜。如果超过40兆字节的数据(以及在VFX中,数据有时可能跨越千兆字节)必须在线程之间完全复制,而不是用户触摸任何内容的每一帧,帧速率将开始爬行。使用不可变(特别是持久性)数据结构,复制变得非常便宜,我们最终只需要复制千字节数据甚至复制史诗场景。这可以足够快地提供所需的60+ FPS。

作为我最初研究不变性概念的另一件事,我做了一个粒子演示(也有400万个粒子:不知怎的,我喜欢4密耳)。在这里,对粒子使用不可变数据结构并不一定有用,但我只是想要一些非常直观的东西来评估它的性能:

enter image description here

每一帧都会创建一个新的粒子集合。我能够使用不可变的持久数据结构在i3上以超过180 FPS的速度完成它。同一个演示版的可变版本运行超过300 FPS,但请记住,粒子模拟非常简单(对每个粒子进行微不足道的处理,如果对每个粒子应用更复杂的逻辑,则差异不会是这样的。粒子)。我不确定是否使数据结构不可变存储粒子从长远来看将是有益的(不能立刻想到我的头顶上有很多帮助的情况),但它主要是自从我最初来自80年代和90年代的游戏开发背景以及我对“可接受的性能”的想法以来,这是一个视觉基准。与帧速率相结合,具有非常直观的心态。所以我喜欢用这种方式对视觉进行基准测试。为GIF看起来像废话的道歉。我很难对它们进行编码,并且必须极大地降低帧速率。原来的明显口吃/滞后也不存在。

  
      
  • 实际上John Carmack可能会让我在gamedev中失业   3D游戏的出现,创造起来要贵得多   这让我在90年代中期成为独立游戏的工作,并把我带到了VFX   在电影和电视以及archviz工作的行业,只有游戏的内容创作,但无论如何我都崇拜他 - 他似乎总是领先十步   在过去的几十年里,我在所有给定的时间点都是我,我就像一只乌龟试图赶上他,总是在他思考的后半年左右。现在他已经完成了不变性和功能编程的潮流,而且我根本不认为他已经变软了。我愿意赌他能做一些非常重要的事情,至少对于gamedev来说。
  •   

答案 1 :(得分:0)

线程可能会更改共享对象的状态,但不一定要更改它们有权访问的所有对象。通常它是用于处理或改变代码流的对象的输入数据。例如,配置通常是不可变的,以防止可能导致混淆不一致状态的并发修改。

答案 2 :(得分:0)

我认为不可变性必须在编程中具有一般的安全感而不是线程安全性,包括避免死锁等。

不变性的概念是指保证。

当在同一线程中运行的线程或不同模块之间共享不可变对象时,不存在有意或无意的突变对线程或模块中的共享对象的负面影响。因此很安全。无论物体位于何处,都可以保证物体保持相同的状态。