在JavaScript中增量是一个原子操作吗?

10 javascript multithreading atomic thread-safety

在JavaScript中增量是一个原子操作吗?如果一个线程正在访问++i;,同时另一个线程 开始访问该操作会有任何问题吗?

Pra*_*kar 10

在Javascript中总是一个函数运行到完成,这意味着如果一个函数正在运行而不是它将完全运行,只有在那之后才会调用另一个函数,因此,语句之间没有交错的可能性(但是在java的情况下它是不同的) ,如果你对异步执行感到困惑而不是总是记得async意味着以后不能并行,那么,遇到你的问题,答案是,不,你不会遇到任何问题,它将是全原子操作.

  • 请注意,这仅适用于*同步*函数。例如,异步 `x = 1; 异步函数 foo() { x += wait 1; }` 将在完成之前在 `await` 处暂停,并在后续循环中完成其余部分(实际分配)。这可能会导致意外结果:https://jsbin.com/zufozat/1/edit?js,console 类似地,生成器函数不一定会运行完成 - `yield` 将暂停其执行,直到请求下一个值为止他们会跑到下一个“产量”等。 (3认同)
  • 你有这方面的资料吗? (2认同)

T.J*_*der 6

\n

如果一个线程正在访问++i; 同时另外一个开始访问操作会不会有问题?

\n
\n

对于像这样的简单变量来说,这种情况不会发生,因为 JavaScript 的定义是在任何给定时间在一个领域i中只能有一个活动线程(代理的执行线程)。(“领域” - 粗略地说,是 JavaScript 全局环境及其中的内容,例如变量。)因此,普通变量或对象属性根本不会出现该问题。您的函数在同步运行期间不能被中断;JavaScript 定义了“运行到完成”语义:每当从作业队列中选取一个“作业”(如触发事件处理程序)并执行时,它就会在执行任何其他作业之前运行到完成。(对于函数,逻辑只能在、、 或处暂停,而不能在同步复合算术运算中间暂停。它可以在涉及 的复合算术运算中间暂停。更多相关信息见下文。类似地,对于生成器函数,他们的逻辑暂停在。)asyncawaitreturnthrowawaityield

\n

您唯一需要担心的地方是如果您使用共享内存,实际内存在领域之间共享,因此确实可以同时被多个线程访问。但如果您这样做,您将SharedArrayBuffer使用 a 处理 a 或类型化数组SharedArrayBuffer,而不是简单的变量或属性。但是,是的,如果处理共享内存,您将体验到 CPU 操作重新排序、过时缓存等所有“光荣”乐趣。Atomics这就是我们拥有对象(包括 )的部分原因Atomics.add,它使用共享内存以原子方式向类型化数组中的元素添加值。(但要注意 na\xc3\xafve 的用法!毕竟,另一个线程可能会在您add完成后、在您读取该值之前覆盖该值...这就是为什么Atomics.add返回新值,而您应该使用新值。)Atomics提供了确保安全访问共享内存所需的裸构建块。(有关此内容的更多信息,请参阅我的书《JavaScript:新玩具:“共享内存和Atomics”》的第 16 章。)

\n

注意:这一切都适用于符合规范的标准 JavaScript 引擎,例如 Web 浏览器和 Node.js 中的引擎。非标准 JavaScript 环境,例如 Java 虚拟机中内置的 JavaScript 脚本支持,可以(当然)定义替代的非标准语义。

\n
\n

Reasync函数:函数中不涉及多线程async。但事实上,函数的逻辑被暂停在 a 处,这await 可能会导致一些令人惊讶的行为。也就是说,它可能发生的地方清楚地标有await

\n

除非你不得不担心,否则我不会担心下面的细节。但对于那些这样做的人...

\n

考虑:

\n

\r\n
\r\n
let a = 1;\n\nasync function one() {\n    return 1;\n}\n\nasync function example() {\n    console.log(`Adding 1 to a`);\n    a += await one();\n}\n\nconsole.log(`Start, a = ${a}`);\nPromise.all([\n    example(),\n    example(),\n    example(),\n])\n.then(() => {\n    console.log(`All done, a = ${a}`);\n});
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

(从技术上讲,我们可以只使用a += await 1;, 因为await会将其操作数包装在隐含的Promise.resolve(x),但我认为显示实际的承诺会更清楚。)

\n

输出:

\n
\n开始,a = 1\na 加 1\na 加 1\na 加 1\n全部完成,a = 2\n
\n

但是等等,我们加了 1a3 次,结果应该是 4,而不是 2?!?!

\n

关键在于await这句话里:

\n
a += await one();\n
Run Code Online (Sandbox Code Playgroud)\n

处理起来是这样的:

\n
    \n
  1. 获取当前值a并将其放在一边;叫它atemp
  2. \n
  3. 称呼one并得到它的承诺。
  4. \n
  5. 等待 Promise 结算并获得履行值;我们称这个值为addend
  6. \n
  7. 评价atemp + addend
  8. \n
  9. 将结果写入a.
  10. \n
\n

或者在代码中:

\n
/* 1 */ const atemp = a;\n/* 2 */ const promise = one();\n/* 3 */ const addend = await promise; // Logic is suspended here and resumed later\n/* 4 */ const result = atemp + addend;\n/* 5 */ a = result;\n
Run Code Online (Sandbox Code Playgroud)\n

(您可以在EvaluateStringOrNumericBinaryExpression中找到此详细信息(您可以在规范中的

\n

在我们使用 的地方example,我们调用了它三次,而没有等待它的承诺解决,因此 Step\xc2\xa01 运行了三次,将a(1) 的值保留了三次。然后,使用这些保存的值a覆盖 \ 的值。

\n

生成器函数在 处具有类似的行为yield,其中它们的逻辑被暂停,然后稍后恢复。

\n

同样,不涉及多线程(并且运行到完成完全完整),只是当函数async的逻辑到达await, return(显式或隐式)或 时throw,函数在该点退出并且返回一个承诺。如果这是因为 an await,那么当函数awaited 的承诺解决时,函数的逻辑将继续,并且将(在正常情况下)最终解决async函数返回的承诺。但这些事情中的每一个都会在单个活动线程上完成。

\n


小智 5

Javascript是单线程的,所以你需要担心解除锁或脏读问题。 为什么 JavaScript 不支持多线程?