我在 JavaScript 规范、与SharedArrayBuffer
相关的建议 DOM 规范扩展或当前的 WHAT-WG HTML 规范中没有看到任何内容,表明当一个线程将消息发布到另一个线程而另一个线程处理消息时,共享内存将跨线程同步/更新。(在一方已将共享内存发送给另一方之后。但是,我也无法通过实验验证它不会发生(在我的测试中,我没有看到过时的值(。有没有这样的保证,我失踪了,如果有,在哪里保证?例如,它是否记录了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;
}
}
});
使用该代码,如果内存未同步,我希望主线程在某个时候看到共享数组中的过时值。但完全有可能(在我看来(这段代码只是碰巧工作,因为消息传递涉及的时间尺度相对较大......
TL;大卫:是的,确实如此。
在es-discuss的帖子中,共享内存提案的作者Lars Hansen写道:
在浏览器中,postMessage 发送和接收始终旨在以与写读对相同的方式创建同步边。 http://tc39.github.io/ecmascript_sharedmem/shmem.html#WebBrowserEmbedding
不确定当规范转移到 es262 文档时,这篇散文最终去了哪里。
我跟进了:
谢谢!
看起来它至少部分在这里: https://tc39.github.io/ecma262/#sec-host-synchronizes-with
所以,一个问题(好吧,两个问题(只适合我们这些不精通的人 在"内存模型"部分的术语中。鉴于:
- 线程 A 通过
postMessage
向线程 B 发送 1k 共享块- 线程 B 直接写入该块中的不同位置(而不是通过
Atomics.store
(- 线程 B 对线程 A 执行
postMessage
(不引用块 在postMessage
(- 线程 A 接收消息并从块读取数据(不是通过
Atomics.load
(。我是否正确,在步骤 4 中可以保证线程 A将可靠地看到步骤 2 中的线程 B 对该块的写入,因为
postMessage
是一个"同步边缘",确保(除其他外( CPU L1d 缓存是最新的,等等?同样,如果(!我没看错,在你的曼德尔布洛特的例子中,你 在共享块中的单个位置具有
Atomics.wait
,并且当 线程唤醒它似乎假定块中的其他数据(不在wait
范围(可以直接可靠地读取。这也是一个"同步" 边缘"?
他回答说:
。确保(除其他事项外( CPU L1d 缓存是最新的,等等?
是的,这就是该语言的意图。对内存的写入应该在postMessage之前发生,接收消息应该在读取之前发生。
。那也是"同步边缘"?
是的,同样的论点。写入发生在唤醒之前,等待的唤醒发生在读取之前。
所有这些都是有意为之,以便允许使用廉价的不同步写入和读取来写入和读取数据,然后进行(相对昂贵的(同步以确保适当的可观察性。