WebWorkers和异步共享数据访问。如何在Scala.js中使用?

时间:2018-04-21 02:33:03

标签: javascript scala asynchronous web-worker scala.js

请考虑一个包含大型JavaScript typed array的Scala.js类,名为 xArr

名为 p( xArr 的进程会将 xArr 作为输入使用,但需要很长时间才能完成。为了避免脚本超时警告, p( xArr 在Web Worker中运行。

回想一下主线程和Web Worker线程之间通信的这些限制:

  1. 任意方向的沟通都采用message passing
  2. 的形式
  3. 消息数据必须符合JavaScript structured clone algorithm
  4. 的要求
  5. 除非在可选传输列表中指定,否则消息数据将被复制,而不是传输到主线程和工作线程。
  6. 要传输消息数据而不是将其复制到工作线程或从工作线程复制,数据必须实现Transferable接口,并且传输列表必须包含对可传输数据的引用。
  7. 如果可转移对象在线程之间传输,则发送线程将失去对它的访问权。
  8. 由于 xArr 的大小,将其副本发送到工作线程会导致严重的内存成本,但由于 p( xArr )< / strong>的运行时间,它无法在主线程中运行。

    幸运的是,类型化数组实现了Transferable接口,因此为了节省计算和内存资源,程序通过传输调用 p( xArr xArr 到WebWorker调用 p( xArr ,然后将 xArr 传回主线程。

    不幸的是,主线程中的其他异步方法必须访问 xArr ,这些方法可能已在调用时转移到工作者的作用域。

    Scala语言功能可以控制对 xArr 的访问,以便在主线程拥有 xArr 时立即执行方法调用,但在工作者拥有时等待它返回范围 xArr

    换句话说:您将如何处理在定义的未定义之间不断交替的类变量?

    你会建议锁吗?承诺/回拨队列?你会以完全不同的方式解决问题吗?如果是这样,怎么样?

    请记住,这是一个Scala.js库,因此我们必须取消对JVM特定功能的限制。

2 个答案:

答案 0 :(得分:2)

我理解你真正的痛苦。这曾用于SharedArrayBuffer,但目前在Chrome中已停用。可悲的是,除了共享内存之外别无选择:

  

请注意,2018年1月5日,所有主流浏览器默认禁用SharedArrayBuffer以响应Spectre。

有计划在完成适当的安全审核后重新添加SharedArrayBuffer。我想我们必须等待。

如果您在Node中运行代码 - 这将很难但可能。

答案 1 :(得分:0)

感谢所有考虑过这个问题的人。截至2018年5月19日存在解决方案;希望更好的人能尽快取代它。

当前版本的工作原理如下:

问题1:我们如何将主线程中的函数调用与工作线程中的函数定义相关联?

S1:Promise对象的映射:Map[Long, PromiseWrapper]()将方法调用ID与可以处理结果的promise相关联。 This simple multiplexing mechanism evolved from another Stack Overflow question。再次感谢Justin du Coeur

问题2:我们如何从主线程中调用工作线程中的函数?

S1:将函数的文本表示传递给worker,然后用eval解析它并调用生成的函数。不幸的是,eval comes with security risks。此外,必须在字符串值中编写纯JavaScript代码会破坏Scala.js的大多数优点,即类型安全和Scala语法。

S2:将函数定义存储在工作线范围的查找表中,并通过传递键来调用函数。这可能有效,但在Scala中感觉很笨,因为不同的函数会采用数量和类型不同的参数。

S3:将函数包装到可序列化的case类中,然后将序列化的字节从主作用域发送到worker作用域并在那里调用函数。您可以将这些案例类视为消息类。目前的解决方案使用这种方法。它依赖于BooPickle Otto Chrons。序列化类包装方法调用和任何平凡的函数参数,例如数字,短字符串和简单的案例类。大数据,如此问题中的TypedArray值,通过后面讨论的机制从主线程转移到工作线程。不幸的是,这种方法意味着必须在编译时定义TypedArray值的所有操作,因为BooPickle依赖于宏而不是反射来序列化和反序列化类。

问题3:如何在不重复的情况下将TypedArray类变量 xArr 的值传递给工作线程和从工作线程传递?

S1:因为xArr符合Transferrable接口,所以它可以完全在主要和工作范围之间传输。同时,包装函数调用的序列化类符合指定具有此签名的apply方法的特征:

def apply(parameters: js.Array[Transferable]): js.Array[Transferable]

按照惯例,parameters数组包含索引0中的消息案例类的序列化版本。后续索引包含TypedArray值。每个消息类都有自己独特的应用方法实现。

问题4:我们如何将计算结果传递回在主线程中等待它的承诺?

S1:问题3.S1中提到的apply方法返回一个新的Transferrable对象数组,其头部有另一个序列化的消息类。该消息类包含计算的返回值: p( xArr ,并且使用自己的apply方法,指示主线程如何解释数组。如果 p( xArr 返回大型对象(如其他TypedArray值),则会占用数组中的后续位置。

问题5:如果主线程中的语句在转移到工作线程时尝试访问 xArr ,该怎么办?

S1。现在,主线程中的任何代码只能通过checkOut方法访问 xArr ,并且必须通过调用checkIn方法来恢复它。 checkOut方法返回一个Future,它在xArr从工作线程返回时完成。对checkOut的并发调用被推送到promises队列。任何调用checkOut的代码都必须调用checkIn将 xArr 的控制权传递给队列中等待的下一个Promise。不幸的是,这种设计使程序员负担了将 xArr 恢复到其包含的类的责任。不幸的是,像这样的方案类似于locks的经典并发模型和malloc and free等内存分配方法,并且倾向于冻结或崩溃的错误代码。

问题5:在工作线程中执行p( xArr )之后, xArr 如何返回到主线程中封装它的类? /强>

S1。用于调用 p( xArr 的消息案例类现在继承自名为Boomerang的特征。顾名思义,这些消息从主线程传输到工作线程,在那里调用 p( xArr ,然后返回,不变,返回主线程。返回主线程后,Boomerang对象调用相关的checkIn方法将 xArr 值恢复为原始的封装对象。

为简单起见,这个答案省略了有关不同类型的可转移参数的细节,改变xArr而不是简单地读取和恢复它的操作,不采取任何参数但仍产生大量TypedArray响应的操作,以及操作它采用了多个大型TypedArray参数,但对上述五种解决方案的微小修改符合这些目标。

以此为基准,我们可以:

简化此设计?

合并用户定义的操作?

查找checkOut,checkIn方法更安全的替代方法?