我是否需要关心异步 Javascript 的竞争条件?

发布于 2024-12-02 01:34:24 字数 611 浏览 6 评论 0原文

假设我加载了一些 Flash 影片,我知道该影片在将来某个时刻将调用 window.flashReady 并设置 window.flashReadyTriggered = true

现在我有一个代码块,我想在闪存准备好时执行它。如果已经调用了 window.flashReady,我希望它立即执行它,如果尚未调用它,我想将其作为回调放在 window.flashReady 中。天真的方法是这样的:

if(window.flashReadyTriggered) {
  block();
} else {
  window.flashReady = block;
}

所以我基于此的担心是 if 条件中的表达式被评估为 false,但在 block() 之前可以执行,window.flashReady由外部Flash触发。因此,永远不会调用block

是否有更好的设计模式来实现我想要的更高级别的目标(例如,手动调用 flashReady 回调)?如果没有,我安全吗?或者我还应该做其他事情吗?

Suppose I load some Flash movie that I know at some point in the future will call window.flashReady and will set window.flashReadyTriggered = true.

Now I have a block of code that I want to have executed when the Flash is ready. I want it to execute it immediately if window.flashReady has already been called and I want to put it as the callback in window.flashReady if it has not yet been called. The naive approach is this:

if(window.flashReadyTriggered) {
  block();
} else {
  window.flashReady = block;
}

So the concern I have based on this is that the expression in the if condition is evaluated to false, but then before block() can be executed, window.flashReady is triggered by the external Flash. Consequently, block is never called.

Is there a better design pattern to accomplish the higher level goal I'm going for (e.g., manually calling the flashReady callback)? If not, am I safe, or are there other things I should do?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

不再见 2024-12-09 01:34:24

所有 Javascript 事件处理程序脚本均由一个主事件队列系统处理。这意味着事件处理程序一次运行一个事件处理程序,并且一个事件处理程序会运行直至完成,然后下一个准备就绪的事件处理程序才会开始运行。因此,Javascript 中不存在在多线程语言中会出现的典型竞争条件,在多线程语言中,该语言的多个线程可以同时运行(或时间切片)并在访问变量时产生实时冲突。

JavaScript 中的任何单个执行线程都会在下一个线程开始之前运行完成。这就是 JavaScript 的工作原理。从事件队列中提取事件,然后开始运行代码来处理该事件。该代码自行运行,直到将控制权返回给系统,然后系统将从事件队列中提取下一个事件并运行该代码,直到将控制权返回给系统。

因此,由两个同时执行的线程引起的典型竞争条件在 Javascript 中不会发生。

这包括所有形式的 Javascript 事件,包括:用户事件(鼠标、按键等)、计时器事件、网络事件(ajax 回调)等...

在 Javascript 中真正可以进行多线程处理的唯一地方是使用<一href="http://blogs.msdn.com/b/davrous/archive/2011/07/15/introduction-to-the-html5-web-workers-the-javascript-multithreading-approach.aspx" rel=" noreferrer">HTML5 Web Workers 或 工作线程(在node.js),但它们与常规 javascript 非常隔离(它们只能通过消息传递与常规 javascript 通信),并且根本无法操作 DOM,并且必须有自己的脚本和命名空间等......


虽然我在技术上不会将此称为竞争条件,Javascript 中存在一些情况,因为它的一些异步操作可能会同时执行两个或多个异步操作(实际上并未执行 Javascript,但底层异步操作正在运行本机代码)同时)并且当每个操作将相对于其他操作完成。这会造成时间的不确定性(如果操作的相对时间对您的代码很重要),从而导致您必须手动编码。您可能需要对操作进行排序,以便一个操作运行,然后您实际上等待它完成,然后再开始下一个操作。或者,您可以启动所有三个操作,然后使用一些代码来收集所有三个结果,当它们全部准备好时,您的代码就会继续。

在现代 Javascript 中,promise 通常用于管理这些类型的异步操作。

因此,如果您有三个异步操作,每个操作都返回一个承诺(例如从数据库读取,从另一个服务器获取请求等...),您可以像这样手动排序:

a().then(b).then(c).then(result => {
    // result here
}).catch(err => {
    // error here
});

或者,如果您希望它们全部运行一起(全部同时飞行)并且只知道它们何时全部完成,您可以这样做:

Promise.all([a(), b(), c()])..then(results => {
    // results here
}).catch(err => {
    // error here
});

虽然我不会将这些竞争条件称为竞争条件,但它们属于设计代码以控制不确定排序的同一个通用系列。


在浏览器中的某些情况下可能会出现一种特殊情况。这并不是真正的竞争条件,但如果您使用大量具有临时状态的全局变量,则可能需要注意。当您自己的代码导致另一个事件发生时,浏览器有时会同步调用该事件处理程序,而不是等到当前执行线程完成。一个例子是:

  1. 单击
  2. click 事件处理程序将焦点更改为另一个字段
  3. 该其他字段有一个 onfocus 事件处理程序
  4. 浏览器立即调用 onfocus 事件处理程序
  5. onfocus 事件处理程序运行
  6. 其余的 click 事件处理程序运行(在 .focus( 之后) ) 调用)

从技术上讲,这不是竞争条件,因为 onfocus 事件处理程序何时执行(在 .focus() 调用期间)是 100% 已知的。但是,它可能会造成一种情况,即一个事件处理程序运行,而另一个事件处理程序正在执行。

All Javascript event handler scripts are handled from one master event queue system. This means that event handlers run one at a time and one runs until completion before the next one that's ready to go starts running. As such, there are none of the typical race conditions in Javascript that one would see in a multithreaded language where multiple threads of the language can be running at once (or time sliced) and create real-time conflict for access to variables.

Any individual thread of execution in javascript will run to completion before the next one starts. That's how Javascript works. An event is pulled from the event queue and then code starts running to handle that event. That code runs by itself until it returns control to the system where the system will then pull the next event from the event queue and run that code until it returns control back to the system.

Thus the typical race conditions that are caused by two threads of execution going at the same time do not happen in Javascript.

This includes all forms of Javascript events including: user events (mouse, keys, etc..), timer events, network events (ajax callbacks), etc...

The only place you can actually do multi-threading in Javascript is with the HTML5 Web Workers or Worker Threads (in node.js), but they are very isolated from regular javascript (they can only communicate with regular javascript via message passing) and cannot manipulate the DOM at all and must have their own scripts and namespace, etc...


While I would not technically call this a race condition, there are situations in Javascript because of some of its asynchronous operations where you may have two or more asynchronous operations in flight at the same time (not actually executing Javascript, but the underlying asynchronous operation is running native code at the same time) and it may be unpredictable when each operation will complete relative to the others. This creates an uncertainty of timing which (if the relative timing of the operations is important to your code) creates something you have to manually code for. You may need to sequence the operations so one runs and you literally wait for it to complete before starting the next one. Or, you may start all three operations and then have some code that collects all three results and when they are all ready, then your code proceeds.

In modern Javascript, promises are generally used to manage these types of asynchronous operations.

So, if you had three asynchronous operations that each return a promise (like reading from a database, fetching a request from another server, etc...), you could manually sequence then like this:

a().then(b).then(c).then(result => {
    // result here
}).catch(err => {
    // error here
});

Or, if you wanted them all to run together (all in flight at the same time) and just know when they were all done, you could do:

Promise.all([a(), b(), c()])..then(results => {
    // results here
}).catch(err => {
    // error here
});

While I would not call these race conditions, they are in the same general family of designing your code to control indeterminate sequencing.


There is one special case that can occur in some situations in the browser. It's not really a race condition, but if you're using lots of global variables with temporary state, it could be something to be aware of. When your own code causes another event to occur, the browser will sometimes call that event handler synchronously rather than waiting until the current thread of execution is done. An example of this is:

  1. click
  2. the click event handler changes focus to another field
  3. that other field has an event handler for onfocus
  4. browser calls the onfocus event handler immediately
  5. onfocus event handler runs
  6. the rest of the click event handler runs (after the .focus() call)

This isn't technically a race condition because it's 100% known when the onfocus event handler will execute (during the .focus() call). But, it can create a situation where one event handler runs while another is in the middle of execution.

千鲤 2024-12-09 01:34:24

JavaScript 是单线程的。没有竞争条件。

当当前“指令指针”处没有更多代码要执行时,“线程”“传递接力棒”,并且排队的 window.setTimeout 或事件处理程序可能会执行其代码。

阅读 node.js 的设计思想,您将更好地理解 Javascript 的单线程方法。

进一步阅读:
为什么 JavaScript 不支持多线程?

JavaScript is single threaded. There are no race conditions.

When there is no more code to execute at your current "instruction pointer", the "thread" "passes the baton", and a queued window.setTimeout or event handler may execute its code.

You will get better understanding for Javascript's single-threading approach reading node.js's design ideas.

Further reading:
Why doesn't JavaScript support multithreading?

老旧海报 2024-12-09 01:34:24

重要的是要注意,如果您例如,您可能仍然会遇到竞争条件。使用多个异步 XMLHttpRequest。未定义返回响应的顺序(即响应可能不会按照发送时的顺序返回)。这里的输出取决于其他不可控事件(服务器延迟等)的顺序或时间。简而言之,这是一个竞争条件。

因此,即使使用单个事件队列(如 JavaScript)也无法阻止事件以不可控制的顺序出现,您的代码应该处理这一点。

It is important to note that you may still experience race conditions if you eg. use multiple async XMLHttpRequest. Where the order of returned responses is not defined (that is responses may not come back in the same order they were send). Here the output depends on the sequence or timing of other uncontrollable events (server latency etc.). This is a race condition in a nutshell.

So even using a single event queue (like in JavaScript) does not prevent events coming in uncontrollable order and your code should take care of this.

沫雨熙 2024-12-09 01:34:24

当然你需要。这种情况一直在发生:

<button onClick=function() {
  const el = document.getElementById("view");
  fetch('/some/api').then((data) => {
    el.innerHTML = JSON.stringify(data);
  })
}>Button 1</button>

<button onClick=function() {
  const el = document.getElementById("view");
  fetch('/some/other/api').then((data) => {
    el.innerHTML = JSON.stringify(data);
  })

}>Button 2</button>

有些人并不将其视为竞争条件。

但确实如此。

竞态条件被广泛地定义为“电子、软件或其他系统的行为,其中输出取决于其他不可控事件的顺序或时间”。

如果用户在短时间内单击这两个按钮,则不保证输出取决于单击的顺序。这取决于哪个 api 请求会更快得到解决。此外,您引用的 DOM 元素可能会被其他一些事件(例如更改路线)删除。

您可以通过禁用按钮或在加载操作正在进行时显示一些微调器来缓解这种竞争状况,但这是作弊。您应该在代码级别有一些互斥体/计数器/信号量来控制异步流程。

要使其适应您的问题,这取决于“block()”是什么。如果是同步函数,则无需担心。但如果它是异步的,你就必须担心:

  function block() {
    window.blockInProgress = true;
    // some asynchronous code
    return new Promise(/* window.blockInProgress = false */);
  }

  if(!window.blockInProgress) {
    block();
  } else {
    window.flashReady = block;
  }

这段代码是有意义的,你想防止块被多次调用。但如果你不在乎,或者“块”是同步的,你就不应该担心。如果您担心检查全局变量值时可能会发生变化,那么您不必担心,除非您调用某些异步函数,否则它保证不会更改。

一个更实际的例子。考虑我们想要缓存 AJAX 请求。

 fetchCached(params) {
   if(!dataInCache()) {
     return fetch(params).then(data => putToCache(data));
   } else {
     return getFromCache();
   }
 }

如果我们多次调用这段代码会发生什么情况?我们不知道哪些数据会先返回,因此也不知道哪些数据会被缓存。前两次它将返回新数据,但第三次我们不知道要返回的响应的形状。

Sure you need. It happens all the time:

<button onClick=function() {
  const el = document.getElementById("view");
  fetch('/some/api').then((data) => {
    el.innerHTML = JSON.stringify(data);
  })
}>Button 1</button>

<button onClick=function() {
  const el = document.getElementById("view");
  fetch('/some/other/api').then((data) => {
    el.innerHTML = JSON.stringify(data);
  })

}>Button 2</button>

Some people don't view it as a race condition.

But it really is.

Race condition is broadly defined as "the behavior of an electronic, software, or other system where the output is dependent on the sequence or timing of other uncontrollable events".

If user clicks these 2 buttons in a brief period, the output is not guaranteed to depend of the order of clicking. It depends on which api request will be resolved sooner. Moreover, the DOM element you're referencing can be removed by some other event (like changing route).

You can mitigate this race condition by disabling button or showing some spinner when loading operation in progress, but that's cheating. You should have some mutex/counter/semaphore at the code level to control your asynchronous flow.

To adapt it to your question, it depends on what "block()" is. If it's a synchronous function, you don't need to worry. But if it's asynchronous, you have to worry:

  function block() {
    window.blockInProgress = true;
    // some asynchronous code
    return new Promise(/* window.blockInProgress = false */);
  }

  if(!window.blockInProgress) {
    block();
  } else {
    window.flashReady = block;
  }

This code makes sense you want to prevent block from being called multiple times. But if you don't care, or the "block" is synchronous, you shouldn't worry. If you're worried about that a global variable value can change when you're checking it, you shouldn't be worried, it's guaranteed to not change unless you call some asynchronous function.

A more practical example. Consider we want to cache AJAX requests.

 fetchCached(params) {
   if(!dataInCache()) {
     return fetch(params).then(data => putToCache(data));
   } else {
     return getFromCache();
   }
 }

So happens if we call this code multiple times? We don't know which data will return first, so we don't know which data will be cached. The first 2 times it will return fresh data, but the 3rd time we don't know the shape of response to be returned.

2024-12-09 01:34:24

是的,Javascript 中当然存在竞争条件。它基于事件循环模型,因此表现出异步计算的竞争条件。以下程序将记录 1016,具体取决于先完成的是 incHead 还是 sqrHead

const rand = () => Math.round(Math.random() * 100);

const incHead = xs => new Promise((res, rej) =>
  setTimeout(ys => {
    ys[0] = ys[0] + 1;
    res(ys);
  }, rand(), xs));

const sqrHead = xs => new Promise((res, rej) =>
  setTimeout(ys => {
    ys[0] = ys[0] * ys[0];
    res(ys);
  }, rand(), xs))

const state = [3];

const foo = incHead(state);

const bar = sqrHead(state);

Promise.all([foo, bar])
  .then(_ => console.log(state));

Yes, of course there are race conditions in Javascript. It is based on the event loop model and hence exhibits race conditions for async computations. The following program will either log 10 or 16 depending on whether incHead or sqrHead is completed first:

const rand = () => Math.round(Math.random() * 100);

const incHead = xs => new Promise((res, rej) =>
  setTimeout(ys => {
    ys[0] = ys[0] + 1;
    res(ys);
  }, rand(), xs));

const sqrHead = xs => new Promise((res, rej) =>
  setTimeout(ys => {
    ys[0] = ys[0] * ys[0];
    res(ys);
  }, rand(), xs))

const state = [3];

const foo = incHead(state);

const bar = sqrHead(state);

Promise.all([foo, bar])
  .then(_ => console.log(state));

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文