`postMessage` 或屈服于事件循环或类似的同步共享内存吗? [英] Does `postMessage` or yielding to the event loop or similar sync shared memory?

查看:129
本文介绍了`postMessage` 或屈服于事件循环或类似的同步共享内存吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在 JavaScript 规范提议的 DOM 规范扩展SharedArrayBuffer当前的 WHAT-WG HTML 规范 表明当一个线程向另一个线程发布消息而另一个线程处理消息时,共享内存将跨线程同步/更新信息.( 一个已经将共享内存发送给另一个之后.)但是,我也无法通过实验验证它不会发生(在我的测试中,我没有看到过时的值).是否有一些这样的保证,我失踪了,如果有,在哪里保证?例如,它是否为 postMessage 进行了记录而我错过了它,或者是否有一些关于屈服于保证它的事件循环/作业队列(因为处理来自另一个线程的消息涉及这样做)), 等等.?或者,是否绝对不能保证(并且该信息在某处的规范中)?

不要推测或做出合理的猜测".我正在寻找硬信息:来自规范来源的引用,一个可复制的实验,表明它不能保证(尽管我认为这是否只是一个实施错误的问题),诸如此类.

<小时>

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

sync-test-postMessage.html:

<头><meta charset="UTF-8"><title>同步测试 postMessage</title><身体><script src="sync-test-postMessage-main.js"></script></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");让计数器 = 0;常量限制 = 1000000;const 报告 = Math.floor(limit/10);让不匹配 = 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") {++计数器;常量值 = 数组 [0];如果(计数器!== 值){++不匹配;console.log(`不同步!${counter} !== ${value}`);}如果(计数器 % 报告 === 0){log(`${counter} of ${limit}`);}如果(计数器<限制){worker.postMessage({type: "pong"});} 别的 {console.log("完成");}}});worker.postMessage({type: "init", array});console.log(`运行到 ${limit}`);

sync-test-postMessage-worker.js:

let 数组;this.addEventListener("message", e => {如果(电子数据){开关(e.data.type){案例初始化":数组 = e.data.array;//落入乒乓"案例乒乓":++数组[0];this.postMessage({type: "ping"});休息;}}});

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

解决方案

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

<小时>

es-discuss 上的主题中,作者共享内存提案 Lars Hansen 写道:

<块引用>

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

当规范被转移到 es262 文档时,不确定这篇散文最终在哪里.

我跟进了:

<块引用>

谢谢!

看起来至少有一部分在这里:https://tc39.github.io/ecma262/#sec-host-同步

所以,一个问题(好吧,两个问题)只针对我们这些不熟悉的人在内存模型部分的术语中.鉴于:

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

...我是否更正,在第 4 步中可以保证线程 A 从步骤 2 中可靠地看到线程 B 对该块的写入,因为postMessage 是一个同步边缘"确保(除其他外)CPU L1d 缓存是最新的,等等?

同样,如果 (!) 我没看错,在你的 Mandlebrot 例子中,你在共享块中的单个位置有一个 Atomics.wait,并且当线程唤醒它似乎假设块中的其他数据(不在wait range) 可以可靠地直接读取.这也是一种同步边缘"?

他回复:

<块引用><块引用>

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

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

<块引用>

...这也是一个同步边缘"?

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

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

I don't see anything in the JavaScript spec, the proposed DOM spec extensions related to SharedArrayBuffer, or the current WHAT-WG HTML spec to suggest that shared memory will be synchronized/updated across threads when one thread posts a message to another and the other processes the message. (After the one has already sent the shared memory to the other.) However, I'm also unable to verify experimentally that it doesn't happen (in my tests, I don't see stale values). Is there some such guarantee that I'm missing, and if so, where is it guaranteed? For instance, is it documented for postMessage and I've missed it, or is there something about yielding back to the event loop / job queue that guarantees it (since handling a message from another thread involves doing that), etc.? Or alternately, is it definitely not guaranteed (and that information is in a spec somewhere)?

Please don't speculate or make a "reasonable guess." I'm looking for hard information: Citations from canonical sources, a replicatable experiment that shows that it isn't guaranteed (although I suppose then there's the question of whether it's just an implementation error), that sort of thing.


Below is the source for my tests that have not yet been able to catch unsynchronized memory. To run it, you'll need to be using a browser that currently supports SharedArrayBuffer, which I think at the moment means Chrome v67 or higher (Firefox, Edge, and Safari all had support but disabled it in response to Spectre and Meltdown in Jan 2018; Chrome did too, but re-enabled it in v67 [July 2018] on platforms where their site-isolation feature is enabled).

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;
        }
    }
});

Using that code, if memory weren't synchronized, I'd expect at some point for the main thread to see a stale value in the shared array. But it's entirely likely (in my view) that this code only happens to work because of the relatively-large timescales involved in the message passing...

解决方案

TL;DR: Yes, it does.


In the thread on es-discuss, the author of the shared memory proposal, Lars Hansen, wrote:

In a browser, postMessage send and receive was always intended to create a synchronization edge in the same way a write-read pair is. http://tc39.github.io/ecmascript_sharedmem/shmem.html#WebBrowserEmbedding

Not sure where this prose ended up when the spec was transfered to the es262 document.

I followed up with:

Thanks!

Looks like it's at least partially here: https://tc39.github.io/ecma262/#sec-host-synchronizes-with

So, a question (well, two questions) just for those of us not deeply versed in the terminology of the Memory Model section. Given:

  1. Thread A sends a 1k shared block to Thread B via postMessage
  2. Thread B writes to various locations in that block directly (not via Atomics.store)
  3. Thread B does a postMessage to Thread A (without referencing the block in the postMessage)
  4. Thread A receives the message and reads data from the block (not via Atomics.load)

...am I correct that in Step 4 it's guaranteed that thread A will reliably see the writes to that block by Thread B from Step 2, because the postMessage was a "synchronization edge" ensuring (amongst other things) that CPU L1d caches are up-to-date, etc.?

Similarly, if (!) I'm reading it correctly, in your Mandlebrot example, you have an Atomics.wait on a single location in a shared block, and when the thread wakes up it seems to assume other data in the block (not in the wait range) can reliably be read directly. That's also a "synchronization edge"?

To which he replied:

...ensuring (amongst other things) that CPU L1d caches are up-to-date, etc.?

Yes, that was the intent of that language. The writes to the memory should happen-before the postMessage and the receive-message should happen-before the reads.

... That's also a "synchronization edge"?

Yes, same argument. The writes happen-before the wake, and the wakeup of the wait happens-before the reads.

All of this is by intent so as to allow data to be written and read with cheap unsynchronized writes and reads, and then for the (comparatively expensive) synchronization to ensure proper observability.

这篇关于`postMessage` 或屈服于事件循环或类似的同步共享内存吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆