【问题标题】:why are transfered buffers neutered in javascript?为什么传输的缓冲区在 javascript 中被绝育?
【发布时间】:2016-07-03 11:53:00
【问题描述】:

Javascript 允许将缓冲区从源线程转移到工作线程。否则,将复制 ArrayBuffer,然后将其传递给 worker。传输的缓冲区在源线程 [1] 中不可访问(“中性”):

// create data that can be transfered
var arr = new Uint8Array(5);

// outputs: 5
console.log(arr.buffer.byteLength);

var worker = new Worker("some_worker.js");

// transfer the buffer
worker.postMessage({arr: arr}, [arr.buff]);

// the buffer vanishes. is "Neutered"
// outputs: 0
console.log(arr.buffer.byteLength);

我了解该机制的工作原理。但是我很好奇为什么要引入它。为什么工作线程之间不共享数据,就像在传统的线程模型中一样,它允许多个线程访问相同的 内存区域?


同一问题的其他措辞进行澄清:

为什么缓冲区在传输时被绝育? / 这种机制背后的原因是什么? / 为什么要介绍它?为什么Workers之间不能共享内存区域?

我正在寻找来自可靠和/或官方来源的答案。


[1]https://developer.mozilla.org/en/docs/Web/API/Worker/postMessage

【问题讨论】:

  • 谢谢,Michal Charemza。我在问题中添加了您的句子,因为这就是问题的原因。

标签: javascript web-worker


【解决方案1】:

Web Workers 中引入了可传输对象,以提高与复制对象相比的性能(尤其是在我们讨论大尺寸对象时)。它可以并行化为常见编程语言(例如 C/C++)中的值传递和引用传递之间的比较。

可能添加了进一步的限制,即不能在源工作线程中使用可转移对象,以保证两个不同线程之间不会出现竞争条件(为了方便开发人员的工作不必关心那个)。此外,还需要在 Javascript 中实现更多的并发原语(例如互斥锁等)。本质上,使用“传输”意味着您只是打算将数据传输到另一个线程,而不是同时从两个线程中使用它们,因此可以说实现是有意义的。

一般来说,Web Workers 不是作为共享内存模型设计的,而是作为消息交换模型设计的。

如需进一步了解性能差异,请查看this。您也可以查看this,其中讨论了为什么 WebKit 中的 Web Workers 没有采用共享内存模型。

【讨论】:

  • +1 表示“一般来说,Web Workers 不是作为共享内存模型设计的,而是作为消息交换模型设计的。”来自最后一个链接的引用:“Web 工作者的主要设计原则是,进行适当的多线程设计的复杂性不会暴露给 JavaScript 开发人员。”
【解决方案2】:

这是一个非常基本的多线程问题。如果主线程和工作线程都可以访问 Array,则必须实现互斥锁,以便在访问缓冲区时不会出现竞争条件。另外,我认为数组缓冲区通常在您需要性能时使用,但是从该缓冲区读取/写入数据的锁定会使工作人员变慢。

我猜这是资源被“移动”而不是共享的原因之一。

TL;DR:多线程

【讨论】:

    【解决方案3】:

    原因是性能。发送的数据没有被复制,ArrayBuffer的所有权转移给接收者。

    对于共享内存,你应该使用SharedArrayBuffer

    【讨论】:

      【解决方案4】:

      根据WHATWG ML,选择是thread safe,因为

      您不能在工作人员之间共享数据。 JS执行的多个线程之间没有(也不可能有)任何共享状态。

      (source)

      还有,

      我们想要 使源 ArrayBuffer 和任何 ArrayBufferViews 的长度为零 在将它们发布给工作人员或返回主线程时。经过 你可以避免来回ping同一个ArrayBuffer 每次迭代分配新的后备存储。

      (source)

      不幸的是,我没有找到关于规格的讨论,the page 应该在哪里托管它会给出 404,我会尝试在其他地方找到副本

      【讨论】:

        【解决方案5】:

        要在 javascript 中使用这些概念,您必须使用这些编码,

        PostMesage(aMessage, transferList)
        

        在 transferList 中必须指定可转移对象,这些对象包含在 aMessage 中:

        var objData =
        {
            str: "string",
            ab: new ArrayBuffer(100),
            i8: new Int8Array(200)
        };
        objWorker.postMessage(objData, [objData.ab, objData.i8.buffer]);
        
        On other side:
        
        self.onmessage = function(objEvent)
        {
            var strText = objEvent.data.str;
            var objTypedArray = objEvent.data.ab;
            var objTypedArrayView = objEvent.data.i8;
        }
        

        要使用“可转让对象”,您实际上是将对象的所有权转移到网络工作者或从网络工作者转移。这就像在没有复制的地方通过引用传递。它与普通的 pass-by-reference 的区别在于,传输数据的一方无法再访问它。

        【讨论】:

          【解决方案6】:

          需要明确的是,transfers 适用于专用和共享工作器,因为它们都使用 MessagePorts

          [1]Dedicated worker 在幕后使用 MessagePort 对象。

          [2]与共享工作者的通信是通过显式 MessagePort 对象完成的

          postMessage 被指定为根据调用者的选择传输或克隆

          [3]port.postMessage(message [, transfer] ) 通过频道发布消息。 传输中列出的对象被传输,而不仅仅是克隆,这意味着它们在发送端不再可用。

          然而,这仅表明发布者是否保留副本,通常基于效率,而不是是否共享任何内存。

          当涉及到“内存”时,它明确规定无论工作人员类型如何或是否传输或克隆数据,都不得共享它

          [4]当用户代理要为具有 URL url、环境设置对象设置对象和 URL 引用者的脚本运行 worker 时,它必须运行以下步骤:

          创建单独的并行执行环境(即单独的线程或进程或等效结构),并在该上下文中运行其余步骤。

          那么现在的问题是:为什么?为什么必须用户代理为所有类型的工作人员创建并行执行环境?

          安全?不,效率? (js什么时候效率高了?),也没有。

          原因是能够遵守或尊重整个规范。如果您关注link [4],您至少会注意到:

          当用户代理要终止工作人员时,它必须与工作人员的主循环并行运行以下步骤(上面定义的“运行工作人员”处理模型):

          1)将worker的WorkerGlobalScope对象的关闭标志设置为true。

          2)如果有任何任务在WorkerGlobalScope对象的事件循环的任务队列中排队,则丢弃它们而不进行处理。

          3)中止当前在worker中运行的脚本。

          4)如果worker的WorkerGlobalScope对象实际上是一个DedicatedWorkerGlobalScope对象(即worker是一个专用的worker),那么清空worker的隐式端口所纠缠的端口的端口消息队列。

          这只是规范的一部分。

          那又是为什么?它是为了能够管理在工作空间中发生的所有事件。实施者必须并行化工作人员,否则会完全发疯。 :)

          【讨论】:

            【解决方案7】:

            它似乎受到历史环境的启发,因为在将工人作为消息传递 API [1] 引入之后,可转移对象被添加到规范中,目的是进行最小的更改 [2] [3]。强>


            [1] https://bugzilla.mozilla.org/show_bug.cgi?id=720083(请求在 Firefox 中实现)

            [2]https://mail.mozilla.org/pipermail/es-discuss/2014-May/037239.html

            因为当 Transferable 的概念在 HTML5 中被形式化时 规范,有一个目标是尽可能减少更改。 Transferable 基本上是 MessagePort 的泛化,它是 以前可以“转移”给网络工作者的唯一类型。 绝育只是规范文本中的一个概念,而不是 IDL。这 Transferable typedef 没有任何关联的方法。唯一的办法 中性对象是将其转移给网络工作者。有 请求提供“close()”方法并使 Transferable 成为 一个新的 Closable 接口的子接口。我们拒绝制作那些 改变,因为它们本质上会引入手动记忆 管理到 JavaScript。

            [3]https://mail.mozilla.org/pipermail/es-discuss/2014-May/037227.html

            首先,一些背景。在设计类型化数组时,它们是 使用 Web IDL 及其 ECMAScript 绑定指定。有过尝试 在类型化数组的开发过程中抛出一些异常 操作——比如超出范围的索引——但这些都是 发现与 Web IDL 或 ECMAScript 不兼容 类似于属性查找的语义。

            【讨论】:

            • 这根本不是原因。拥有共享资源要么需要全局解释器锁,这将破坏首先拥有 Worker 的目的,要么拥有同步对象,这在 JS 中是相当陌生的。
            • @transistor09 你能详细说明为什么这不是一个原因吗?向现有设施引入一个小补丁,而不是指定和实施第二个完全不同的系统,这对我来说很有意义。
            • 您引用的内容不是原因;这是规范中最小的导致的更改,以允许一次仅由一个上下文共享但拥有的对象(原因已经提到)。
            猜你喜欢
            • 2022-10-02
            • 2016-08-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-10-03
            • 1970-01-01
            相关资源
            最近更新 更多