我在将SharedArrayBuffer同步到主线程时遇到了问题。
下面是一个场景:
我有两名员工负责我的程序的不同方面。第一个工作人员负责对象交互,第二个工作人员负责计算可见性等,主线程将进行可视化。
首先,第一个工作人员创建一个具有以下布局的SharedArrayBuffer:
new SharedArrayBuffer(112);
[
Lock: 4 Byte
MetaInfo: 4 Byte
Location: 12 Byte
Scale: 12 Byte
Rotation: 16 Byte
Matrix: 64 Byte
]然后,他将SAB发送给主线程和第二个工作人员,并将位置、比例和旋转属性存储在缓冲区中。每次更新字段时,他都会锁定SAB,更新值,并将MetaInfo字段(transform标志)的第一个位设置为true。
如果设置了转换标志,第二个工作人员将从给定的位置、尺度和旋转字段计算矩阵,并将其保存在矩阵字段中。之后,MetaInfo字段的第二个位(矩阵标记)将被设置为true。
如果设置了矩阵标志,则主线程现在需要读取最终矩阵。
问题来了:在工作人员上,可以使用lock字段上的Atomics.wait方法锁定缓冲区。但是主线程缺乏这样的特性,导致口吃和“跳跃”。是否有一致的方法来阻止其他工人在阅读过程中写入SAB?
下面是我的SharedArrayBuffer包装器的代码:
class SharedObject {
SharedBuffer: SharedArrayBuffer; // the shared array buffer
Lock: Int32Array; // view for lockíng the buffer
MetaInfo: Int32Array; // view for meta info
Location: Float32Array;
constructor(buffer) {
// if valid buffer is passed assign it to this object
if (buffer !== undefined && buffer instanceof SharedArrayBuffer && buffer.byteLength == 112) {
this.SharedBuffer = buffer;
} else {
// create new shared array buffer
this.SharedBuffer = new SharedArrayBuffer(112);
}
this.Lock = new Int32Array(this.SharedBuffer, 0, 4);
this.MetaInfo = new Int32Array(this.SharedBuffer, 4, 8);
[ ... init the rest of the views ... ]
// init the lock element
if (buffer === undefined) {
Atomics.store(this.Lock, 0, 1);
}
}
lock() {
Atomics.wait(this.Lock, 0, 0);
Atomics.store(this.Lock, 0, 0);
return true;
}
free() {
if (Atomics.wake(this.Lock, 0, 1) == 0) {
Atomics.store(this.Lock, 0, 1);
}
return true;
}
setFlag(flag) {
this.MetaInfo[0] = this.MetaInfo[0] | flag;
}
isFlagSet(flag) {
return (this.MetaInfo[0] & flag) > 0;
}
resetFlag(flag) {
this.MetaInfo[0] = this.MetaInfo[0] - (this.MetaInfo[0] & flag);
}
}注意,锁和空闲方法不能在主线程中使用,因为:
注意:此操作仅适用于共享的Int32Array,不允许在主线程上运行。
这种设置是否可能在一个SharedArrayBuffer中有多个独立的字段,或者是否应该考虑对每个应用程序使用多个SharedArrayBuffer。
发布于 2017-08-01 14:59:42
经过研究,防止主线程使用Atomics.wait()的选择似乎是避免同步线程阻塞,因为主线程处理用户事件和页面呈现以及其他服务,并且允许Atomics.wait()会导致web应用程序上的用户体验差。
API接口之后是API接口,它目前还没有在Chrome上实现,但可以在火狐上使用。
使用屏幕外的画布,您可以从用于呈现的web工作者中提取Atomics.wait(),在从共享数组缓冲区读取数据之后应用gl操作,然后调用gl.commit(),这将将gl框架呈现给主线程。
不幸的是,由于火狐是目前唯一支持OffscreenCanvas API的浏览器,而NW.js只支持Chrome,这个特殊的同步挑战似乎无法克服,因为在Chrome上的同一线程中缺乏对Atomics.wait()和WebGL的支持。
发布于 2019-02-22 12:49:34
不要认为这个问题仍然相关。然而,如果有人面临同样的问题,以下是我的想法:
Atomics.wait(this.Lock, 0, 0); // <-- 2 threads can check 'lock' flag one by one
// and pass to the next line
Atomics.store(this.Lock, 0, 0); // <-- then they both set 0 as a 'lock' flag
// and move further
return true;
}所以,你会有比赛的条件。
另外,请查看我为测试SharedArrayBuffer https://github.com/vrudkovskiy/textediting-test-js/blob/master/packages/client/src/utils/SharingLock.ts而编写的这个演示应用程序。
更新。
Atomics类仅在单个缓冲区元素上提供原子操作。但是,正如我所理解的,您需要锁定对整个缓冲区的访问,同时一些线程从/对其进行读/写。
因此,例如,如果有2个线程将数据写入内存的同一部分,则可能出现以下情况:
线程1写: 11111111
线程2写: 22222222
结果可以是: 11221122 -这将是一些随机的结果。
而且,不幸的是,在Atomics中没有原子信号量,应该使用Atomics.compareExchange和Atomics.wait调用的组合来实现它。这真的不容易,因为有很多情况你应该涵盖,这就是为什么我说不做自己。你可以找到更好的解释这里
在本例中,主线程多次尝试锁定共享内存,比方说,每个帧(呈现都不是冻结的),一旦成功,它就会呈现数据。
更新。
主线程不访问数据,它只能尝试再次阻塞数据。当然会有性能上的缺陷。
- worker calculated some data
- it sends a notification to main thread about it
- worker does not use that part of memory till main thread allows it by notifying back through postMessage function
更新。
您可以尝试将主线程中的tryLock与工作线程中的框架绘图同步结合起来(以某种方式预测主线程何时绘制数据,并在那时不碰它),听起来像是一种解决办法,我不确定是否可能:)
或者,完全不阻止对缓冲区的访问是可以的(只需绘制像11221122这样的结果),所以只需使用Atomics来读取/更改单个元素。或者锁定对缓冲区的一些小部分的访问。
在任何情况下,没有理想的多线程解决方案,您总是在性能和数据一致性之间进行选择。
https://stackoverflow.com/questions/45439334
复制相似问题