12

我在JavaScript规范提议SharedArrayBuffer的与处理消息。(一个已经将共享内存发送到另一个之后。)但是,我也无法通过实验验证它不会发生(在我的测试中,我没有看到陈旧的值)。是否有一些我错过的保证,如果有,在哪里保证?例如,它是否记录在postMessage我错过了它,或者是否有一些关于返回到事件循环/作业队列来保证它(因为处理来自另一个线程的消息涉及这样做)等等?或者,是否绝对不能保证(并且该信息在某处的规范中)?

不要推测或做出“合理的猜测”。我正在寻找确凿的信息:来自规范来源的引文,一个可复制的实验,表明它不能保证(尽管我认为它是否只是一个实现错误的问题),诸如此类的事情。


下面是我的测试的源代码,这些测试还不能捕获不同步的内存。要运行它,您需要使用当前支持的浏览器,SharedArrayBuffer我认为目前这意味着 Chrome v67 或更高版本(Firefox、Edge 和 Safari 都支持,但为了响应 2018 年 1 月的 Spectre 和 Meltdown 而禁用了它; Chrome 也这样做了,但在 v67 [2018 年 7 月] 在启用了站点隔离功能的平台上重新启用了它)。

sync-test-postMessage.html

<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Sync Test postMessage</title>
</head>
<body>
<script src="sync-test-postMessage-main.js"></script>
</body>
</html>

sync-test-postMessage-main.js

const array = new Uint32Array(new SharedArrayBuffer(Uint32Array.BYTES_PER_ELEMENT));
const worker = new Worker("./sync-test-postMessage-worker.js");
let counter = 0;
const limit = 1000000;
const report = Math.floor(limit / 10);
let mismatches = 0;
const now = performance.now();
const log = msg => {
    console.log(`${msg} - ${mismatches} mismatch(es) - ${performance.now() - now}ms`);
};
worker.addEventListener("message", e => {
    if (e.data && e.data.type === "ping") {
        ++counter;
        const value = array[0];
        if (counter !== value) {
            ++mismatches;
            console.log(`Out of sync! ${counter} !== ${value}`);
        }
        if (counter % report === 0) {
            log(`${counter} of ${limit}`);
        }
        if (counter < limit) {
            worker.postMessage({type: "pong"});
        } else {
            console.log("done");
        }
    }
});
worker.postMessage({type: "init", array});
console.log(`running to ${limit}`);

sync-test-postMessage-worker.js

let array;
this.addEventListener("message", e => {
    if (e.data) {
        switch (e.data.type) {
            case "init":
                array = e.data.array;
                // fall through to "pong"
            case "pong":
                ++array[0];
                this.postMessage({type: "ping"});
                break;
        }
    }
});

使用该代码,如果内存未同步,我希望主线程在某个时候看到共享数组中的陈旧值。但完全有可能(在我看来)这段代码只是碰巧工作,因为消息传递涉及相对较大的时间尺度......

4

1 回答 1

2

TL;DR:是的,确实如此。


es-discuss 的帖子中,共享内存提案的作者 Lars Hansen 写道:

在浏览器中, postMessage 发送和接收总是旨在以与读写对相同的方式创建同步边缘。http://tc39.github.io/ecmascript_sharedmem/shmem.html#WebBrowserEmbedding

不确定当规范转移到 es262 文档时这篇散文在哪里结束。

我跟进了:

谢谢!

看起来它至少部分在这里: https ://tc39.github.io/ecma262/#sec-host-synchronizes-with

所以,一个问题(嗯,两个问题)只针对我们这些对记忆模型部分的术语不熟悉的人。鉴于:

  1. 线程 A 通过 向线程 B 发送一个 1k 的共享块postMessage
  2. 线程 B 直接写入该块中的各个位置(不是 via Atomics.store
  3. 线程 BpostMessage对线程 A 执行 a(不引用 中的块postMessage
  4. 线程A接收消息并从块中读取数据(不是通过 Atomics.load

...我是否更正了,在第 4 步中,可以保证线程 A 可靠地看到第 2 步中线程 B 对该块的写入,因为这 postMessage是一个“同步边缘”,确保(除其他外)CPU L1d 缓存已启动-迄今为止,等等?

同样,如果(!)我正确阅读它,在您的 Mandlebrot 示例中,您Atomics.wait在共享块中的单个位置上有一个,当线程唤醒时,它似乎假定块中的其他数据(不在 wait范围内) 可以可靠地直接读取。这也是“同步边缘”?

他回答说:

...确保(除其他外)CPU L1d 缓存是最新的,等等?

是的,这就是该语言的意图。对内存的写入应该发生在 postMessage 之前,而接收消息应该发生在读取之前。

...这也是“同步边缘”?

是的,同样的论点。写入发生在唤醒之前,等待的唤醒发生在读取之前。

所有这些都是有意的,以便允许使用廉价的非同步写入和读取来写入和读取数据,然后用于(相对昂贵的)同步以确保适当的可观察性。

于 2018-10-26T15:09:15.057 回答