且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

我对异步操作的理解正确吗?

更新时间:2023-11-29 14:30:04

几乎正确,但不太正确.

Almost correct but not quite.

如果您正在谈论异步性",英文单词这个词,就意味着事情可能会发生混乱.此概念已在许多语言中使用,包括Java和C/C ++中的多线程.

If you are talking about "asynchronousness" the word in the English language then it just means that things can happen out of order. This concept is used in a lot of languages including multithreading in Java and C/C++.

如果您正在谈论异步性的特定概念,因为它与C/C ++中的node.js或异步I/O有关,那么您对它在底层如何工作确实有一些误解.

If you are talking about the specific concept of asynchronousness as it relates to node.js or asynchronous I/O in C/C++ then you do have some misunderstandings in how this works at the low level.

如果一段代码需要很长时间才能完成,则可能会出现问题.这是因为i)它停止了下面的代码的运行,并且在将硬代码加载到后台的同时运行它可能会更好.ii)实际上,JS可能会在硬代码完成之前尝试执行以下代码.如果下面的代码依赖于硬代码,那就有问题.

A problem can occur if a piece of code takes a long time to complete. This is because i) it stops code below from running and it might be nice to run this whilst the hard code loads in the background. And ii) Indeed, JS might try to execute the code below before the hard code’s finished. If the code below relies on the hard code, that’s a problem.

当谈论C/C ++中的javascript或异步I/O(javascript从那里获得异步性)时,这是不正确的.

When talking about javascript or asynchronous I/O in C/C++ (where javascript got its asynchronousness from) this is not true.

实际上发生的事情是等待某件事发生可能需要很长时间才能完成.与其等着,为什么不告诉操作系统一旦事情发生就执行一些代码(您的回调).

What actually happens is that waiting for something to happen may take a long time to complete. Instead of waiting why not tell the OS to execute some code (your callback) once that thing happens.

在OS级别,大多数现代操作系统都具有API,可让您告诉它在发生某些情况时唤醒您的进程.那可能是键盘事件,鼠标事件,I/O事件(来自磁盘或网络),系统重新配置事件(例如,更改显示器分辨率)等.

At the OS level most modern operating systems have API that let you tell it to wake your process up when something happens. That thing may be a keyboard event, a mouse event, an I/O event (from disk or network), a system reconfiguration event (eg. changing monitor resolution) etc.

大多数传统语言实现阻塞I/O.发生的情况是,当您尝试从磁盘或网络读取内容时,进程会立即进入睡眠状态,并且操作系统将在数据到达时再次将其唤醒:

Most traditional languages implement blocking I/O. What happens is that when you try to read something form disk or network your process goes to sleep immediately and the OS will wake it up again when the data arrives:

Traditional blocking I/O

 time
  │
  ├────── your code doing stuff ..
  ├────── read_data_from_disk() ───────────────────┐
  ┆                                                ▼
  :                                        OS puts process to sleep
  .
  .                                         other programs running ..
  .
  :                                            data arrives ..
  ┆                                        OS wakes up your process   
  ├────── read_data_from_disk() ◀──────────────────┘
  ├────── your program resume doing stuff ..
  ▼

这意味着您的程序一次只能等待一件事.这意味着您的程序大多数时候都没有使用CPU.侦听更多事件的传统解决方案是多线程.每个线程都将分别阻塞其事件,但是您的程序可以为它感兴趣的每个事件生成一个新线程.

This means that your program can only wait for one thing at a time. Which means that most of the time your program is not using the CPU. The traditional solution to listen to more events is multithreading. Each thread will seperately block on their events but your program can spawn a new thread for each event it is interested in.

事实证明,每个线程等待一个事件的幼稚多线程处理速度很慢.而且最终会消耗大量RAM,尤其是对于脚本语言.因此,这不是javascript所做的.

It turns out that naive multithreading where each thread waits for one event is slow. Also it ends up consuming a lot of RAM especially for scripting languages. So this is not what javascript does.

注意:从历史上看,javascript使用单线程而不是多线程是一个偶然的事实.这只是团队做出的决定的结果,该决定将渐进式JPEG渲染和GIF动画添加到了早期的浏览器中.碰巧的是,这正是使node.js之类的东西快速运行的原因.

Note: Historically the fact that javascript uses a single thread instead of multithreading is a bit of an accident. It was just the result of decisions made by the team that added progressive JPEG rendering and GIF animations to early browsers. But by happy coincidence this is exactly what makes things like node.js fast.

javascript要做的是等待多个事件,而不是等待单个事件.所有现代操作系统都有API,可让您等待多个事件.它们的范围是队列/ kqueue select() 系统调用.

What javascript does instead is wait for multiple events instead of waiting for a single event. All modern OSes have API that lets you wait for multiple events. They range from queue/kqueue on BSD and Mac OSX to poll/epoll on Linux to overlapped I/O on Windows to the cross-platform POSIX select() system call.

javascript处理外部事件的方式类似于以下内容:

The way javascript handles external events is something like the following:

Non-blocking I/O (also known as asynchronous I/O)

 time
  │
  ├────── your code doing stuff ..
  ├────── read_data_from_disk(read_callback) ───▶ javascript stores
  │                                               your callback and
  ├────── your code doing other stuff ..          remember your request
  │
  ├────── wait_for_mouse_click(click_callback) ─▶ javascript stores
  │                                               your callback and
  ├────── your code doing other stuff ..          remember your request
  │
  ├────── your finish doing stuff.
  ┆       end of script ─────────────▶ javascript now is free to process
  ┆                                  pending requests (this is called
  ┆                                  "entering the event loop").
  ┆                                  Javascript tells the OS about all the
  :                                  events it is interested in and waits..
  .                                            │
  .                                            └───┐
  .                                                ▼
  .                                        OS puts process to sleep
  .
  .                                         other programs running ..
  .
  .                                            data arrives ..
  .                                        OS wakes up your process
  .                                                │
  .                                            ┌───┘
  :                                            ▼
  ┆                       Javascript checks which callback it needs to call
  ┆                          to handle the event. It calls your callback.
  ├────── read_callback() ◀────────────────────┘
  ├────── your program resume executing read_callback
  ▼

主要区别在于同步多线程代码在每个线程中等待一个事件.诸如javascript之类的单线程或诸如Nginx或Apache之类的多线程异步代码会在每个线程中等待多个事件.

The main difference is that synchronous multithreaded code waits for one event per thread. Asynchronous code either single threaded like javascript or multi threaded like Nginx or Apache wait for multiple events per thread.

注意:Node.js在单独的线程中处理磁盘I/O,但所有网络I/O均在主线程中处理.这主要是因为异步磁盘I/O API在Windows和Linux/Unix之间不兼容.但是,可以在主线程中执行磁盘I/O.Tcl语言是在主线程中执行异步磁盘I/O的一个示例.

Note: Node.js handles disk I/O in separate threads but all network I/O are processed in the main thread. This is mainly because asynchronous disk I/O APIs are incompatible across Windows and Linux/Unix. However it is possible to do disk I/O in the main thread. The Tcl language is one example that does asynchronous disk I/O in the main thread.

一种解决方案是:如果一项操作需要很长时间才能完成,则您希望在处理原始线程的同时在单独的线程中进行处理.

A solution is: if an operation takes a long time to complete, you want to process it in a separate thread while the original thread is processed.

除了Web worker(或Node.js中的worker线程)之外,javascript中的异步操作不会发生这种情况.如果是网络工作者,则可以,您是在其他线程中执行代码.

This is not what happens with asynchronous operations in javascript with the exception of web workers (or worker threads in Node.js). In the case of web workers then yes, you are executing code in a different thread.

但是,即使事件队列也可能遭受与主线程相同的问题.如果位于fetch2上方的fetch1需要很长的时间才能返回承诺,而fetch2却没有,则JS可能会在执行fetch1之前开始执行fetch2

But even the event-que can suffer from the same problem as the main-thread. If fetch1, which is positioned above fetch2, takes a long time to return a promise, and fetch2 doesn’t, JS might start executing fetch2 before executing fetch1

这不是正在发生的事情.您正在执行的操作如下:

This is not what is happening. What you are doing is as follows:

fetch(url_1).then(fetch1); // tell js to call fetch1 when this completes
fetch(url_2).then(fetch2); // tell js to call fetch2 when this completes

并非js可能"开始执行.上面的代码发生的事情是两个提取都是同步执行的.也就是说,第一次抓取严格发生在第二次抓取之前.

It is not that js "might" start executing. What happens with the code above is both fetches are executed synchronously. That is, the first fetch strictly happens before the second fetch.

但是,以上所有代码所做的就是告诉javascript在稍后的某个时间回调函数 fetch1 fetch2 .这是要记住的重要课程.上面的代码不会执行 fetch1 和 fetch2 函数(回调).您要做的就是告诉javascript在数据到达时调用它们.

However, all the above code does is tell javascript to call the functions fetch1 and fetch2 back at some later time. This is an important lesson to remember. The code above does not execute the fetch1 and fetch2 functions (the callbacks). All you are doing is tell javascript to call them when the data arrives.

如果您执行以下操作:

fetch(url_1).then(fetch1); // tell js to call fetch1 when this completes
fetch(url_2).then(fetch2); // tell js to call fetch2 when this completes

while (1) {
    console.log('wait');
}

然后 fetch1 fetch2 将永远不会执行.

我会在这里暂停,让您考虑一下.

I'll pause here to let you ponder on that.

记住异步I/O的处理方式.实际上,所有I/O(通常称为异步)功能调用都不会立即导致I/O的访问.他们所做的只是提醒javascript,您需要某些东西(单击鼠标,网络请求,超时等),并且您希望javascript在该事件完成时稍后执行功能.仅当没有更多代码要执行时,才在脚本末尾处理异步I/O .

Remember how asynchronous I/O is handled. All I/O (often called asynchronous) function calls don't actually cause the I/O to be accessed immediately. All they do is just remind javascript that you want something (a mouse click, a network request, a timeout etc.) and you want javascript to execute your function later when that thing completes. Asynchronous I/O are only processed at the end of your script when there is no more code to execute.

这确实意味着您不能在JavaScript程序中使用无限while循环.不是因为javascript不支持它,而是有一个内置的while循环围绕着您的整个程序:这个大的while循环称为事件循环.

This does mean that you cannot use an infinite while loop in a javascript program. Not because javascript does not support it but there is a built-in while loop that surrounds your entire program: this big while loop is called the event loop.

另外,我在链接时已读过.那么,这算是一个异步操作.

On a separate note, I’ve read when chaining .then, that counts as one asynchronous operation.

是的,这是设计使然,以避免在处理承诺时使人们感到困惑.

Yes, this is by design to avoid confusing people on when promises are processed.

如果您对操作系统如何处理所有这些而不需要进一步创建线程感兴趣,那么您可能会对我对以下相关问题的回答感兴趣:

If you are interested at how the OS handles all this without further creating threads you may be interested in my answers to these related questions:

节点js-发生了什么在回调执行期间处理传入事件

如果没有别的我想让你了解两件事:

If nothing else I'd like you to understand two things:

  1. Javascript是一种严格的同步编程语言.您的代码中的每个语句都严格按顺序执行.

  1. Javascript is a strictly synchronous programming language. Each statement in your code is executed strictly sequentially.

所有语言的异步代码(是的,包括C/C ++以及Java和Python等)将在以后的任何时间调用您的回调.您的回调不会立即被调用.异步性是一个函数调用级别的概念.

Asynchronous code in all languages (yes, including C/C++ and Java and Python etc.) will call your callback at any later time. Your callback will not be called immediately. Asynchronousness is a function-call level concept.

当涉及到异步性*时,并不是说javascript有什么特别之处.只是大多数javascript库默认情况下都是异步的(尽管您也可以用任何其他语言编写异步代码,但它们的库通常默认情况下是同步的.)

It's not that javascript is anything special when it comes to asynchronousness*. It's just that most javascript libraries are asynchronous by default (though you can also write asynchronous code in any other language but their libraries are normally synchronous by default).

*注意:当然,诸如async/await之类的确使javascript更具有处理异步代码的能力.

*Note: of course, things like async/await does make javascript more capable of handling asynchronous code.

旁注:承诺没有什么特别的.这只是一种设计模式.它不是javascript语法内置的东西.只是Promises随附了较新版本的javascript,并将其作为其标准库的一部分.即使在非常老版本的javascript和其他语言中,您也可能一直使用诺言(例如,在Java8及更高版本中,调用在其标准库中具有诺言,但将它们称为Future).

Side note: Promises are nothing special. It is just a design pattern. It is not something built-in to javascript syntax. It is just that newer versions of javascript comes with Promises as part of its standard library. You could have always used promises even with very old versions of javascript and in other languages (Java8 and above for example call have promises in their standard library but call them Futures).