Javascript将SharedArrayBuffer同步到主线程

问题描述 投票:2回答:2

我在将SharedArrayBuffer同步到主线程时遇到问题。

这是场景:

我有两个工作人员处理我的程序的不同方面。第一个工作人员负责对象交互,第二个工作人员负责计算可见性等,主要线程将负责可视化。

起初,第一个Worker创建了一个具有以下布局的SharedArrayBuffer

new SharedArrayBuffer(112);
[   
    Lock:      4 Byte
    MetaInfo:  4 Byte
    Location: 12 Byte
    Scale:    12 Byte
    Rotation: 16 Byte
    Matrix:   64 Byte
]

然后,他将SAB发送到主线程和第二个Worker,并将位置比例和旋转属性存储在Buffer中。每次他更新字段时,他都会锁定SAB,更新值并将MetaInfo字段的第一位(转换标志)设置为true。

如果设置了变换标志,则第二个Worker将根据给定的位置比例和旋转字段计算矩阵,并将其保存在Matrix字段中。之后,MetaInfo字段的第二位(矩阵标志)将设置为true。

如果设置了矩阵标志,主线程现在需要读取最终矩阵。

问题出现了:在工作人员上,可以使用锁定字段上的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,并且不允许在主线程上使用。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait

这种设置甚至可以在一个SharedArrayBuffer中有多个独立的字段,或者我应该考虑为每个应用程序使用多个SharedArrayBuffer

javascript multithreading web-worker
2个回答
1
投票

在做了一些研究之后,似乎防止主线程使用Atomics.wait()的选择是避免同步线程阻塞,因为主线程处理用户事件和页面呈现以及其他服务,并且允许Atomics.wait()会导致差Web应用程序的用户体验。

SharedArrayBuffer API之后是OffscreenCanvas API,目前仍然没有在Chrome上实现,但可以在Firefox上使用。

使用屏幕外画布,您可以从用于渲染的Web工作者中使用Atomics.wait(),在从共享数组缓冲区读取数据后应用gl操作,然后调用gl.commit(),这会将gl帧渲染到主线程。

不幸的是,由于Firefox是目前唯一支持OffscreenCanvas API的浏览器,而NW.js仅适用于Chromium,因此在Chrome上的同一个线程中缺乏对Atomics.wait()和WebGL的支持,这种特殊的同步挑战似乎无法克服。


1
投票

不要认为问题仍然存在。但是,如果有人面临同样的问题,我的想法如下:

  1. 不要自己实现锁机制,使用像https://github.com/lars-t-hansen/js-lock-and-condition/blob/master/lock.js这样的库 这是锁定功能的问题:
        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中没有原子semaphore,它应该使用Atomics.compareExchange和Atomics.wait调用的组合来实现。这真的不容易,因为你应该覆盖很多案例,这就是为什么我说不要自己做。你可以找到更好的解释here

  1. 在这个问题中,不需要阻塞主线程,而是主线程在主线程读取时阻止其他人访问共享内存。 TryLock功能可以使用https://github.com/lars-t-hansen/js-lock-and-condition/blob/master/lock.js#L126

在这种情况下,主线程会进行多次尝试来锁定共享内存,假设每个帧(渲染都不会冻结),一旦成功,它就会呈现数据。

更新。

主线程不访问数据,它只能再次尝试阻止数据。当然会有性能缺陷。

  1. 另一种技术是不使用Atomics而是发布消息。例如: 工人计算了一些数据 它向主线程发送关于它的通知 worker通过postMessage函数通知主要线程,直到主线程允许它为止
  2. 最后一点。上面的锁库的作者已经为异步锁定标准写了一个提议https://github.com/tc39/proposal-atomics-wait-async它包含一个polyfill,它使用第三个worker来锁定主线程的数据,换句话说主线程委托将资源锁定到通知父线程的单独worker关于成功锁定。 Polyfill包含明显的性能缺陷,但本机实现应该更快

更新。

您可以尝试将主线程中的tryLock与工作线程中的帧绘制同步相结合(以某种方式预测主线程何时绘制数据并且当时不触摸它),听起来像是一种解决方法,我不确定它是否可能:)

或者,也许根本不阻止访问缓冲区(只是绘制像11221122这样的结果),所以只需使用Atomics来读取/更改单个元素。或者仅锁定访问缓冲区的一些小部分。

在任何情况下都没有多线程的理想解决方案,您总是在性能和​​数据一致性之间做出选择。

© www.soinside.com 2019 - 2024. All rights reserved.