请考虑一个包含大型JavaScript typed array的Scala.js类,名为 xArr 。
名为 p( xArr )的进程会将 xArr 作为输入使用,但需要很长时间才能完成。为了避免脚本超时警告, p( xArr )在Web Worker中运行。
回想一下主线程和Web Worker线程之间通信的这些限制:
由于 xArr 的大小,将其副本发送到工作线程会导致严重的内存成本,但由于 p( xArr )< / strong>的运行时间,它无法在主线程中运行。
幸运的是,类型化数组实现了Transferable接口,因此为了节省计算和内存资源,程序通过传输调用 p( xArr ) xArr 到WebWorker调用 p( xArr ),然后将 xArr 传回主线程。
不幸的是,主线程中的其他异步方法必须访问 xArr ,这些方法可能已在调用时转移到工作者的作用域。
Scala语言功能可以控制对 xArr 的访问,以便在主线程拥有 xArr 时立即执行方法调用,但在工作者拥有时等待它返回范围 xArr
换句话说:您将如何处理在定义的和未定义之间不断交替的类变量?
你会建议锁吗?承诺/回拨队列?你会以完全不同的方式解决问题吗?如果是这样,怎么样?
请记住,这是一个Scala.js库,因此我们必须取消对JVM特定功能的限制。
答案 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方法更安全的替代方法?