ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScriptの非同期シングルスレッドの解析(画像とテキスト)

JavaScriptの非同期シングルスレッドの解析(画像とテキスト)

不言
不言オリジナル
2018-09-10 16:55:421351ブラウズ

この記事の内容はJSの非同期シングルスレッドの解析(画像と文章)ですので、お困りの方は参考にしていただければ幸いです。

一般の開発者 (特に並列コンピューティングやマルチスレッドの背景知識を持つ開発者) にとって、js の非同期処理は非常に奇妙です。結果として、この奇妙な現象は、js の「シングルスレッド」機能によって引き起こされます。

私はかつてこの部分を教科書的な「まず定義してから拡張する」という方法で説明しようとしたことがありますが、非常に苦痛でした。なぜなら、この物事の背後にある詳細を明らかにし、それを一般化し、より高い視点から問題を見るには、実際には多くの基礎知識が必要だからです。この知識を明確に説明して終わると、オペレーティングシステムやコンピュータネットワークなどの催眠術の本の数章を読者に強制的に読ませるようなもので、本当に退屈で退屈です。

そしてもっと重要なことは、そのステップに到達した時点で、読者のエネルギーは使い果たされており、元の問題、つまりなぜ js の非同期処理がおかしいのかを気にするエネルギーがなくなっているということです。

そこで、私は逆のことをすることにしました。初心者のように何も知らないところから始めましょう。

最初に「間違った概念」を使用して議論を開始し、次にコードを使用して概念の違反を発見します。 。

さらに修正を加え、いくつかの例を再度検討し、まだ満足のいく明確でない点があるかどうかを考え、調整します。このようにして、私たちは探偵のように、完全に正しくない仮説から出発し、常に証拠を探し、常に仮説を修正し、最終的に完全な真実に到達するまで段階的に追求していきます。

この書き方は、その人の本当の知識探求と研究のプロセスにより一致しており、「探求の問題」についてより多くのインスピレーションをもたらすことができると思います。この考え方や研究コンセプトは、一般的な知識よりも重要だと思います。そうすることで、授乳を待つ赤ん坊であることを強いられるのではなく、自分で食べ物を探し回ることができる知識のハンターになることができます。

それでは、JS コードの一部から探索の旅を始めましょう。

console.log('No. 1');

setTimeout(function(){
    console.log('setTimeout callback');
}, 5000);

console.log('No. 2');

出力結果は次のとおりです:

No. 1
No. 2
setTimeout callback

このコードには複雑なものはほとんどなく、すべて print ステートメントです。唯一の特別な関数は setTimeout です。 大まかなオンライン情報によると、この関数は 2 つのパラメータを受け入れます: setTimeout,根据粗略的网上资料显示,它接受两个参数:

  • 第一个参数是callback函数,就是让它执行完之后,回过头来调用的函数。

  • 另一个是时间参数,用于指定多少微妙之后,执行callback函数。这里我们使用了5000微妙,也即是5秒钟。

另一个重点是,setTimeout是一个异步函数,意思是我的主程序不必去等待setTimeout执行完毕,将它的运行过程扔到别的地方执行,然后主程序继续往下走。也即是,主程序是一个步调、setTimeout是另一个步调,即是“异步”的方式跑代码。

如果你有一些并行计算或者多线程编程的背景知识,那么上面的语句就再熟悉不过了。如果在多线程环境,无非是另起一根线程去运行打印语句console.log('setTimeout callback')。然后主线程继续往下走,新线程去负责打印语句,清晰明了。

所以综合起来,这段代码的意思是,主线程执行到语句setTimeout时,就把它交给“其它地方”,让这个“其它地方”等待5秒钟之后运行。而主线程继续往下走,去执行“No. 2”的打印。所以,由于其它部分要等待5秒钟之后才运行,而主线程立刻往下运行了“No. 2”的打印,最终的输出结果才会是先打印“No. 2”,再打印“setTimeout callback”。

嗯,so far so good。一切看来都比较美好。

如果我们对上述程序做一点变动呢?例如,我可不可以让“setTimeout callback”这个信息先被打印出来呢?因为在并行计算中,我们经常遇到的问题便是,由于你不知道多个线程之间谁执行得快、谁执行得慢,所以我们无法判定最终的语句执行顺序。这里我们让“setTimeout callback”停留了5秒钟,时间太长了,要不短一点?

console.log('No. 1');

setTimeout(function(){
    console.log('setTimeout callback');
}, 1);

console.log('No. 2');

我们将传递给setTimeout

  • 最初のパラメータはコールバックです。実行後にコールバックされる関数。

  • もう 1 つは時間パラメータで、コールバック関数が実行されるまでの秒数を指定するために使用されます。ここでは 5000 マイクロ秒、つまり 5 秒を使用します。 🎜
🎜もう 1 つの重要な点は、setTimeout は非同期関数であるということです。つまり、メイン プログラムは setTimeout が完了するまで待つ必要がありません。実行中のプロセスは実行のために別の場所にスローされ、メイン プログラムは続行されます。つまり、メイン プログラムはプログラムであり、setTimeout は別のプログラムであり、コードを「非同期」で実行する方法です。 🎜🎜並列コンピューティングまたはマルチスレッド プログラミングに関する背景知識がある場合は、上記のステートメントは非常に馴染みのあるものとなるでしょう。マルチスレッド環境の場合、新しいスレッドを開始して print ステートメント console.log('setTimeout callback') を実行するだけです。その後、メインスレッドはダウンし続け、新しいスレッドはステートメントを明確かつ明確に出力します。 🎜🎜要約すると、このコードの意味は、メインスレッドがステートメント setTimeout を実行するときに、それを「他の場所」に渡し、この「他の場所」を 5 秒間待機させてから実行するということです。ランニング。 。メインスレッドは引き続きダウンし、「No.2」の印刷を実行します。したがって、他の部分は実行前に 5 秒待機する必要があり、メインスレッドはすぐに「No. 2」の出力を実行するため、最終的な出力結果は、最初に「No. 2」を出力し、次に「setTimeout」を出力することになります。折り返し電話" "。 🎜🎜まあ、ここまでは順調です。すべてが良く見えます。 🎜🎜上記の手順にいくつかの変更を加えたらどうなるでしょうか?たとえば、「setTimeout callback」メッセージを最初に出力させることはできますか?並列コンピューティングでよく遭遇する問題は、複数のスレッドの中で誰が速く実行し、誰が遅く実行するのかがわからないため、最終的なステートメントの実行順序を決定できないことです。ここでは、「setTimeout callback」を 5 秒間保持します。時間が長すぎます。これを短くする必要があります。 🎜
console.log('No. 1');

setTimeout(function(){
    console.log('setTimeout callback');
}, 0);

console.log('No. 2');
🎜 setTimeout に渡されるパラメータを 1 ミリ秒に変更しました。何度か実行した後、結果が変わっていないことがわかりますか? !ちょっと異常な気がしますが、もう少し小さくしてはどうでしょうか? 0に変更しますか? 🎜
console.log('No. 1');

setTimeout(function(){
    console.log('setTimeout callback');
}, 0);

for (let i = 0; i 🎜何度も実行しましたが、まだ変更できないことがわかりました。これは実際には少し奇妙です。なぜなら、通常の並列コンピューティングやマルチスレッド プログラミングでは、複数の実行を通じて、実際にさまざまな予測できない結果が表示されるからです。ここでは、同じ実行シーケンスの結果が魔法のように得られます。これは異常です。 🎜<p>但我们还无法完全下一个肯定的结论,可不可能因为是<code>setTimeout</code>的启动时间太长,而导致“No. 2”这条语句先被执行呢?为了做进一步的验证,我们可以在“No. 2”这条打印语句之前,加上一个<code>for</code>循环,给<code>setTimeout</code>充分的时间去启动。</p><pre class="brush:php;toolbar:false">console.log('No. 1');

setTimeout(function(){
    console.log('setTimeout callback');
}, 0);

for (let i = 0; i <p>运行这段代码,我们发现,"No. 1"这条打印语句很快地显示到了浏览器命令行,等了一秒钟左右,接着输出了</p><pre class="brush:php;toolbar:false">No. 2
setTimeout callback

诶?!这不就更加奇怪了吗?!setTimeout不是等待0秒钟后立刻运行吗,就算启动再慢,也不至于等待一秒钟之后,还是无法正常显示吧?况且,在加入这个for循环之前,“setTimeout callback”这条输出不是立刻就显示了吗?

综合这些现象,我们有理由怀疑,似乎“setTimeout callback”一定是在“No. 2”后显示的,也即是:setTimeout的callback函数,一定是在console.log('No. 2')之后执行的。为了验证它,我们可以做一个危险一点的测试,将这个for循环,更改为无限while循环。

console.log('No. 1');

setTimeout(function(){
    console.log('setTimeout callback');
}, 0);

while {}  // dangerouse testing

console.log('No. 2');

如果setTimeout的callback函数是按照自己的步调做的运行,那么它就有可能在某个时刻打印出“setTimeout callback”。而如果真的是按照我们猜测的那样,“setTimeout callback”必须排在“No. 2”之后,那么浏览器命令行就永远不会出现“setTimeout callback”。

运行后发现,在浏览器近乎要临近崩溃、达到内存溢出的情形下,“setTimeout callback”依旧没有打印出来。这也就证明了我们的猜测!

这里,我们第一次出现了理念和现实的矛盾。按照通常并行计算的理念,被扔到“其它地方”的setTimeout callback函数,应该被同时运行。可事实却是,这个“其它地方”并没有和后一条打印“No. 2”的语句共同执行。这时候,我们就必须要回到基础,回到js这门语言底层的实现方式上去追查,以此来挖掘清楚这后面的猫腻。

js的特性之一是“单线程”,也即是从头到尾,js都在同一根线程下运行。或许这是一个值得调查深入的点。想来,如果是多线程,那么setTimeout也就该按照我们原有的理念做执行了,但事实却不是。而这两者的不同,便在于单线程和多线程上。

找到了这个不同点,我们就可以更深入去思考一些细节。细想起来,所谓“异步”,就是要开辟某个“别的地方”,让“别的地方”和你的主运行路线一起运行。可是,如果现在是单线程,也就意味着计算资源有且只有一份,请问,你如何做到“同时运行”呢?

这就好比是,如果你去某个办事大厅,去缴纳水费、电费、天然气。那么,我们可以粗略地将它们分为水费柜台、电费柜台、天然气柜台。那么,如果我们依次地“先在水费柜台办理业务,等到水费的明细打印完毕、缴纳完费用后;再跑去电费柜台打印明细、加纳费用;再跑去天然气柜台打印明细、加纳费用”,这就是一个同步过程,必须等待上一个步骤做完后,才能做下一步。

而异步呢,就是说我们不必在某个环节浪费时间瞎等待。比如,我们可以在“打印水费明细”的空闲时间,跑到电费和天然气柜台去办理业务,将“电费明细、天然气明细的打印”这两个任务提前启动起来。再回过头去缴纳水费、缴纳电费、缴纳天然气费用。其实,这就是华罗庚推广优选法的时候举的例子,烧水、倒茶叶、泡茶,如何安排他们的顺序为高效。

显然,异步地去做任务更高效。但这要有一个前提,就是你做任务的资源,也即是干活的人或者机器,得有多份才行。同样按照上面的例子来展开讨论,虽然有水费、电费、天然气这三个柜台,可如果这三个柜台背后的办事人员其实只有一个呢?比如你启动了办理水费的业务,然后想要在办理水费业务的等待期,去电费柜台办理电费业务。表面上,你去电费柜台下了申请单,请求办理电费业务,可却发现根本没有办事员去接收你的这个业务!为何?因为这有且只有一个的办事员,还正在办理你的水费业务啊!这时候,你的这个所谓的“异步”,有何意义?!

所以从这个角度来看,当计算资源只有一份的时候,你做“异步”其实是没什么意义的。因为干活的资源只有一份,就算在表面做了名义上的“异步”,可最终就像上面的多柜台单一办事员那样,到了执行任务层面,还是会一个接一个地完成任务,这就没有意义了。

つまり、jsの特徴は「シングルスレッド」+「非同期」ということですが、まさに私たちが議論している「意味のない」状況ではないでしょうか? !では、なぜ一度に意味のないことをしなければならないのでしょうか?

そうですね...物事は面白くなってきました。

一般的に言えば、何か魔法のようなものや奇妙なものが出来事の中に現れる場合、それは基本的に私たちが特定の細部を見落としているか、特定の詳細について誤解や誤解があることが原因です。問題を解決するためには、既存の資料を常に見直し、検証を繰り返すことで、見落としていたいくつかのトリックを発見することができます。

js の非同期に関するプロモーション ビデオを見てみましょう。通常、js 非同期の必要性を説明するために、ブラウザーのリソースの読み込みとページのレンダリングの間の矛盾が引用されます。

レンダリングは、大まかに「絵」を描くプロセスとして理解できます。たとえば、ブラウザがページ上にボタンや画像を表示したい場合、Web ページ上に「画像」を描画するアクションが必要です。または、オペレーティング システムがモニタ上にグラフィカル インターフェイス「デスクトップ」を表示したい場合は、対応する「ピクチャ」をモニタ上に描画する必要があります。まとめると、この「描き出す」作業を「レンダリング」といいます。

たとえば、ページ上のボタンをクリックすると、ブラウザはバックエンド データベースに移動してデータ レポートを取得し、Web ページに数値を表示します。また、js が非同期をサポートしていない場合、Web ページ全体が滞留し、マウスがボタンをクリックしてもページはその後のレンダリング作業を完了できません。プログラム フローは、バックエンドがデータをフロントエンドに返すまで続行できません。

ここで、jsの「非同期」とは、実際には、ブラウザが「読み込み」タスクを「他の場所」に割り当てることを許可し、「読み込み処理」と「レンダリング処理」を同期的に進めることができるようにすることです。

待って、またここは「別の場所」ですか? ! !

js はシングルスレッドだと言われていますが、コンピューティング リソースは 1 つだけではないでしょうか? どのようにして「同時にロードしてレンダリング」できるのでしょうか? !なんてこと、冗談ですか? !

クソ、この中のどの文が真実ですか? ! jsはシングルスレッドだというのは本当ですか?それとも、ブラウザは「読み込みとレンダリングを同時に行う」ことができるというのは本当ですか? !

この疑問を解決するにはどうすればよいですか? !明らかに、ブラウザがどのように設計されているかを確認するには、ブラウザの内部を深く調べる必要があります。

検索エンジンでは、ブラウザや JS に関する検索を行うことで、基本的な情報を得るのは難しくありません。 JS がブラウザのすべてではありません。ブラウザが担当するのは JS エンジンと呼ばれるブラウザのコンポーネントだけです。 Chrome で使用されている最も有名なものは、有名な V8 エンジンで、js の解析と実行を担当します。

その一方で、js を使用する大きな理由は、DOM 要素を自由に操作でき、Ajax 非同期リクエストを実行でき、最初に示した例のように setTimeout非同期タスク割り当てを行います。これらはすべて js の優れた機能です。 <code>setTimeout做异步任务分配。这些都是js优秀特性。

可令人惊讶的事情来了,当我们去探索这个掌管js一切的V8引擎的时候,我们却发现,它并不提供DOM的操控、Ajax的执行以及setTimeout

しかし、ここで驚くべきことが起こりました。js に関するすべてを制御する V8 エンジンを調査したところ、DOM 制御、Ajax 実行、および setTimeout 機能が提供されていないことがわかりました。

上の写真はAlexander Zlatkovからのもので、その構造は次のとおりです: JavaScriptの非同期シングルスレッドの解析(画像とテキスト)

JSエンジン

  1. メモリヒープ

  • コールスタック

  • Web API

  • DOM (ドキュメント)

    • Ajax (XMLHttpRequest)

    • Timeout (setTimeout)

    • Callback Queue

  • Event Loop

  • 明らかにこれはjsの機能ですが、なぜこれらの関数があるのですか制御するためにjsエンジンによって実装されていませんそれ?まあ、面白い~~~

    えっ! 「シングルスレッド」ではなく、ロードプロセスが別の場所にスローされていませんか? ! js はシングルスレッドです。つまり、js は js エンジン内でシングルスレッドであり、コンピューティング リソースの 1 つの共有のみを割り当てることができます。しかし、データをロードするための Ajax 機能は js エンジンに配置されていませんか? !草!なんてクソババアのキツネなんだ! 「シングルスレッド」と「ロードとレンダリングを同時に行う」という 2 つの記述のうち、どちらか 1 つだけが正しいと思っていましたが、両方とも正しいことが分かりました。なぜ?なぜなら、js がシングルスレッドであると言われているだけで、ブラウザ自体がシングルスレッドであるとは言われていないからです。したがって、レンダリング関連の js 部分とデータ読み込みの Ajax 部分は、基本的に 2 つのモジュール、つまり 2 つのスレッドになっているため、同時に実行できます。もちろん並行して行うことも可能です!なんと!
  • 诶~等等,让我们再仔细看看上面这张图呢?!Ajax不在js引擎里,可是setTimeout也不在js引擎里面啊!!如果Web APIs这部分是在不同于js引擎的另外一根线程里,它们不就可以实现真正意义上的并行吗?!那为何我们开头的打印信息“setTimeout callback”,无法按照并行的方式,优先于“No. 2”打印出来呢?

    嗯......真是interesting......事情果然没有那么简单。

    显然,我们需要考察更多的细节,特别是,每一条语句在上图中,是按照什么顺序被移动、被执行的。

    谈到语句的执行顺序,我们需要再一次将关注点放回到js引擎上。再次回看上面这幅结构图,JS引擎包含了两部分:一个是 memory heap,另一个是call stack。前者关于内存分配,我们可以暂时放下。后面即是函数栈,嗯,它就是要进一步理解执行顺序的东西。

    函数栈(call stack)为什么要叫做“栈(stack)”呢?为什么不是叫做函数队列或者别的神马?这其实可以从函数的执行顺序上做一个推断。

    函数最开始被引进,其实就是为了代码复用和模块化。我们期望一段本该出现的代码,被单独提出来,然后只需要用一个函数调用,就可以将这段代码的执行内容给插入进来。

    所以,如果当我们执行一段代码时,如果遇到了函数调用,我们会期望先去将函数里面的内容执行了,再跳出来回到主程序流,继续往下执行。

    所以,如果把一个函数看作函数节点的话,整个执行流程其实是关于函数节点的“深度优先”遍历,也即是从主函数开始运行的函数调用,整个呈深度优先遍历的方式做调用。而结合算法和数据结构的知识,我们知道,要实现“深度遍历”,要么使用递归、要么使用stack这种数据结构。而后者,无疑更为经济使用。

    所以咯,既然期望函数调用呈深度优先遍历,而深度优先遍历又需要stack这种数据结构做支持,所以维护这个函数调用的结构就当然呈现为stack的形式。所以叫做函数栈(stack)。

    当然,如果再发散思考一下,操作系统的底层涌来维护函数调用的部分也叫做函数栈。那为何不用递归的方式来实现维护呢?其实很简单,计算机这么个啥都不懂的东西,如何知道递归和返回?它只不过会一往无前的一直执行命令而已。所以,在没有任何辅助结构的情况下,能够一往无前地执行的方式,只能是stack,而不是更为复杂的递归概念的实现。

    另一方面,回到我们最开头的问题,矛盾其实是出现在setTimeout的callback函数上。而上面的结构图里,还有一部分叫做“callback queue”。显然,这一部分也是我们需要了解的东西。

    结合js的call stack和callback queue这两个关键词,我们不难搜索到一些资料,来展开讨论这两部分是如何同具体的语句执行相结合的。

    先在整体上论述一下这个过程:

    • 正常的语句执行,会一条接一条地压入call stack,执行,再根据执行的内容继续压入stack。

    • 而如果遇到有Web APIs相关的语句,则会将相应的执行内容扔到Web APIs那边。

    • Web APIs这边,可以独立于js引擎,并行地分配给它的语句,如Ajax数据加载、setTimeout的内容。

    • Web APIs这边的callback function,会在在执行完相关语句后,被扔进“callback queue”。

    • Event loop会不断地监测“call stack”和“callback queue”。当“call stack”为空的时候,event loop会将“callback queue”里的语句压入到stack中,继续做执行。

    • 如此循环往复。

    以上内容比较抽象,让我们用一个具体的例子来说明。这个例子同样来自于Alexander Zlatkov。使用它的原因很简单,因为Zlatkov在blog中使用的说明图,实在是相当清晰明了。而目前我没有多余的时间去使用PS绘制相应的结构图,就直接拿来当作例子说明了。

    让我们考察下面的代码片段:

    console.log('Hi');
    setTimeout(function cb1() { 
        console.log('cb1');
    }, 5000);
    console.log('Bye');

    哈哈,其实和我们使用的代码差不多,只是打印的内容不同。此时,在运行之前,整个底层的结构是这样的:

    JavaScriptの非同期シングルスレッドの解析(画像とテキスト)

    然后,让我们执行第一条语句console.log('Hi'),也即是将它压入到call stack中:

    JavaScriptの非同期シングルスレッドの解析(画像とテキスト)

    その後、JS エンジンはスタックの一番上のステートメントを実行します。これに応じて、ブラウザのコンソールには「こんにちは」というメッセージが出力されます:

    JavaScriptの非同期シングルスレッドの解析(画像とテキスト)

    このステートメントが実行されると、スタックからも消えます:

    JavaScriptの非同期シングルスレッドの解析(画像とテキスト)

    2 番目のステートメント を再度押します。 setTimeout: setTimeout

    JavaScriptの非同期シングルスレッドの解析(画像とテキスト)

    执行setTimeout(function cb1() { console.log('cb1'); }, 5000);

    JavaScriptの非同期シングルスレッドの解析(画像とテキスト)

    注意,由于setTimout部分并没有被包含在js引擎中,所以它就直接被扔给了Web APIs的Timeout部分。这里,stack中的蓝色部分起到的作用,就是将相应的内容“timer、等候时间5秒、回调函数cb1”扔给Web APIs。然后这条语句就可以从stack中消失了:

    JavaScriptの非同期シングルスレッドの解析(画像とテキスト)

    继续压入下一条语句console.log('Bye')

    JavaScriptの非同期シングルスレッドの解析(画像とテキスト)

    注意,此时在Web APIs的部分,正在并行于js引擎执行相应的语句,即:等候5秒钟。Okay,timer继续它的等待,而stack这边已经有语句了,所以需要把它执行掉:

    JavaScriptの非同期シングルスレッドの解析(画像とテキスト)

    相应的浏览器控制台,就会显示出“Bye”的信息。而stack中运行后的语句,就该消失:

    JavaScriptの非同期シングルスレッドの解析(画像とテキスト)

    此时,stack已经为空。Event loop检测到stack为空,自然就想要将callback queue中的语句压入到stack中。可此时,callback queue中也为空,于是Event loop只好继续循环检测。

    另一方面,Web APIs这边的timer,并行地在5秒钟后开始了它的执行——什么也不做。然后,将它相应的回调函数cb1(),放到callback queue中:

    JavaScriptの非同期シングルスレッドの解析(画像とテキスト)

    Event loop由于一直在循环检测,此时,看到callback queue有了东西,就迅速将它从callback queue中取出,然后将其压入到stack里:

    JavaScriptの非同期シングルスレッドの解析(画像とテキスト)

    现在Stack里有了东西,就需要执行回调函数cb1()。而cb1()里面调用了    console.log('cb1')这条语句,所以需要将它压入stack中:

    JavaScriptの非同期シングルスレッドの解析(画像とテキスト)

    stack继续执行,现在它的最上层是    console.log('cb1'),所以需要先执行它。于是浏览器的控制它打印出相应的信息“cb1”:

    JavaScriptの非同期シングルスレッドの解析(画像とテキスト)

    将执行了的    console.log('cb1')语句弹出栈:

    JavaScriptの非同期シングルスレッドの解析(画像とテキスト)

    继续执行cb1()剩下的语句。此时,cb1()

    🎜49384 -5781de390c5f95f2.png🎜🎜🎜setTimeout(function cb1() { console.log('cb1'); }, 5000);:🎜🎜🎜 JavaScriptの非同期シングルスレッドの解析(画像とテキスト)🎜🎜 🎜 以降に注意してください。 >setTimout 部分は js エンジンには含まれておらず、Web API の Timeout 部分に直接スローされます。ここで、スタックの青い部分の役割は、対応する内容「タイマー、待ち時間5秒、コールバック関数cb1」をWeb APIに投げることです。その後、次のステートメントがスタックから消える可能性があります: 🎜🎜🎜JavaScriptの非同期シングルスレッドの解析(画像とテキスト)🎜🎜🎜次のステートメント console.log('Bye') のプッシュを続けます: 🎜🎜🎜JavaScriptの非同期シングルスレッドの解析(画像とテキスト)🎜🎜🎜現時点では、Web API パートでは、対応するステートメントは JS エンジンと並行して実行されます。つまり、5 秒間待機します。さて、タイマーは待機を続けますが、すでにスタックにステートメントがあるため、それを実行する必要があります: 🎜🎜🎜JavaScriptの非同期シングルスレッドの解析(画像とテキスト)🎜🎜🎜 対応するブラウザ コンソールに「Bye」メッセージが表示されます。スタック内で実行した後のステートメントは消えるはずです: 🎜🎜🎜JavaScriptの非同期シングルスレッドの解析(画像とテキスト)🎜🎜🎜現時点では、スタックは空です。イベント ループはスタックが空であることを検出し、当然のことながらコールバック キュー内のステートメントをスタックにプッシュしようとします。ただし、この時点ではコールバック キューも空であるため、イベント ループはループ検出を続行する必要があります。 🎜🎜一方、Web API 側のタイマーは、何もせずに 5 秒後に並列実行を開始します。次に、対応するコールバック関数 cb1() をコールバック キューに入れます: 🎜🎜🎜JavaScriptの非同期シングルスレッドの解析(画像とテキスト)🎜🎜🎜イベントループがループして検出しています。このとき、コールバックキューに何かがあることを確認すると、すぐにコールバックキューから取り出してスタックにプッシュします: 🎜🎜🎜JavaScriptの非同期シングルスレッドの解析(画像とテキスト)🎜🎜🎜スタックに何かがあるので、コールバック関数 cb1() を実行する必要があります。そして、cb1() はステートメント console.log('cb1') を呼び出すため、スタックにプッシュする必要があります: 🎜🎜🎜JavaScriptの非同期シングルスレッドの解析(画像とテキスト)🎜🎜🎜スタックは実行を継続します。最上位は console.log('cb1') なので、最初に実行する必要があります。したがって、ブラウザは、対応する情報「cb1」を出力するように制御します。 🎜🎜🎜JavaScriptの非同期シングルスレッドの解析(画像とテキスト)🎜🎜🎜実行された console.log('cb1') ステートメントをスタックからポップします: 🎜🎜🎜JavaScriptの非同期シングルスレッドの解析(画像とテキスト)🎜🎜🎜実行を継続するcb1()残りのステートメント。現時点では、cb1() には実行する必要のある他のステートメントがありません。つまり、すでに実行されているため、スタックからポップアウトします。 🎜

    JavaScriptの非同期シングルスレッドの解析(画像とテキスト)

    整个过程结束!如果从头到尾看一遍的话,就是下面这个gif图了:

    JavaScriptの非同期シングルスレッドの解析(画像とテキスト)

    相当清晰直观,对吧!

    如果你想进一步地把玩js的语句和call stack、callback queue的关系,推荐Philip Roberts的一个GitHub的开源项目:Loupe,里面有他online版本供你做多种尝试。

    有了这些知识,现在我们回过头去看开头的那段让人产生疑惑的代码:

    console.log('No. 1');
    
    setTimeout(function(){
        console.log('setTimeout callback');
    }, 0);
    
    console.log('No. 2');

    按照上面的js处理语句的顺序,第一条语句console.log('No. 1')会被压入stack中,然后被执行的是setTimout

    根据我们上面的知识,它会被立刻扔进Web APIs中。可是,由于这个时候我们给它的等待时间是0,所以,它的callback函数console.log('setTimeout callback')会立刻被扔进“Callback Queue”里面。所以,那个传说中的“其它地方”指的就是callback queue。

    那么,我们能够期望这一条console.log('setTimeout callback')先于“No. 2”被打印出来吗?

    其实是不可能的!为什么?因为要让它被执行,首先它需要被压入到call stack中。可是,此时call stack还没有将程序的主分支上的语句执行完毕,即还有console.log('No. 2')这条语句。所以,event loop在stack还未为空的情况下,是不可能把callback queue的语句压入stack的。所以,最后一条“setTimeout callback”的信息,一定是会排在“No. 2”这条信息后面被打印出来的!

    这完全符合我们之前加入无限while循环的结果。因为主分支一直被while循环占有,所以stack就一直不为空,进而,callback queue里的打印“setTimeout callback”的语句就更不可能被压入stack中被执行。

    探索到这里,似乎该解决的问题也都解决了,好像就可以万事大吉,直接封笔走人了。可事实却是,这才是我们真正的泛化讨论的开始!

    做研究和探索,如果停留于此,就无异于小时候自己交作业给老师,目的仅仅是完成老师布置的任务。在这里,这个老师布置的任务就是文章开头所提出的让人疑惑的代码。可是,解决这段代码并不是我们的终极目的。我们需要泛化我们的所学和所知,从更深层次的角度去探索,为什么我们会疑惑,为什么一开始无法发现这些潜藏在表面之下不同。我们要继续去挖掘,我们到底在哪些最根本的问题上出现了误解和错误认识,从而导致我们一路如此辛苦,无法在开头看到事情的真相。

    回顾我们的历程,一开始让我们载跟斗的,其实就是对“异步”和“多线程”的固定假设。多线程了,就是异步,而异步了,一定是多线程吗?我们潜意识里是很想做肯定回答的。这是因为如果异步了,但却是单线程,整个异步就没有意义了(回忆那个多柜台、单一办事员的例子)。可js却巧妙地运用了:使用异步单线程去分配任务,而让真正做数据加载的Ajax、或者时间等待的setTimeout的工作,扔给浏览器的其它线程去做。所以,本质上js虽然是单线程的,可在做实际工作的时候,却利用了浏览器自身的多线程。这就好比是,虽然是多柜台、单一办事员,可办事员将缴纳电费、水费的任务,外包给其它公司去做,这样,虽然自己仍然是一个办事员,但却由于有了外包服务的支持,依旧可以一起并行来做。

    另一方面,js的异步、单线程的特性,逼迫我们去把并行计算中的“同步/异步、阻塞/非阻塞”等概念理得更清楚。

    “同步”的英文是synchronize,但在中文的语境下,却很容易和“同时”挂钩。于是,在潜意识里有可能会有这样一种联想,“同步”就是“同时”,所以,一个同步(synchronize)的任务就被理解为“可以一边做A,一边做B”。而这个潜意识的印象,其实完全是错误的(一般做A一边做B,其实是“异步”+“并行”的情况)。

    但在各类百科词典上,确实有用“同时”来作为对“同步”的解释。这是为什么呢?其实这是对”同步“用作”同时“的一个混淆理解。如果仔细考虑”同时“的意思,细分起来,其实是有两种理解:

    • 同時に、例えば午前9時にAとBの両方を行っています。

    • もう 1 つは同じ時間基準システム、つまり壁にあるいわゆる時計も同じです。

    前者はわかりやすいので、ここでは後者を中心に説明します。たとえば、私は中国本土にいる米国のクラスメートと WeChat 音声チャットを開きました。それは私の側では 22:00、彼の側では 9:00 でした。私たちがチャットするとき、私たちは同じ時間にいますが、同じ時間基準システム (壁の時計) の中にいるわけではありません。コンピュータで議論される同期は、実際には後者の「同じ参照システム」について議論されています。同期とは、参照システムをまとめて同じシステムの下に置くことを意味します。

    もう 1 つの例として、人生において、「コンピューターを同期する」「携帯電話のアドレス帳を同期する」「フォト アルバムを同期する」と言うのは簡単ですが、どういう意味ですか?これは、さまざまなクライアント (PC、携帯電話、サーバー) のコンテンツの一貫性を保つためです。つまり、全員が一貫した参照システムに配置されます。 PC には写真 A がありますが、携帯電話には写真 A はなく、写真 B があるとは言わないでください。 このとき、PC 上の情報について話す人と、 の情報について話す人がいます。電話でも同じことを話しています。その理由は、誰もが同じ基準の枠組みに置かれていないからです。

    つまり、同期とは、全員が壁の時計を一貫して同じペースになるように調整することを「同時に」指します。つまり、同時に、同じ時間基準システムを意味します。物事が同時に並行して起こるのではなく。当然のことですが、非同期とは、各人の時間参照システムが異なることを意味します。たとえば、私は中国本土にいますが、私たちの時間参照システムは異なるものであり、同じではありません。周波数帯域で。

    実際、すべての独立した個人とすべての独立したコンピューティング リソースは、独自の参照システムを表します。タスクを他の人または他のコンピューティング リソースに分散する限り、2 つの参照システムが表示されます。1 つは元のメイン ブランチの参照システムで、もう 1 つは新しいコンピューティング リソースの参照システムです。並列コンピューティングには、ステートメント バリアを使用して、すべての計算ブランチがこの位置ノードで計算を完了できるようにする同期メカニズムがあります。なぜそれが同期メカニズムと言われるのでしょうか?統一参照システムについての私たちの理解によれば、他のすべての計算ブランチが計算を完了することを保証するものであり、これにより他のブランチも確実に消滅し、メイン ブランチのみが参照システムとして残されます。そのため、誰もが誤解なく同じことについて話し、同じ言葉を言うことができます。

    一方、js の設計をより深く理解したい場合は、シングルコアのタイムシェアリング システムの時代など、コンピューターの歴史の初期に戻る必要があると思います。当時、オペレーティング システムのハードウェア制限は、ブラウザの JS エンジンの制限と同じくらい大きかった。同じ制限の下で、以前のオペレーティング システムは、非常に限られたコンピューティング リソースをどのように巧みに使用して、オペレーティング システム全体に滑らかさ、スムーズさ、強力な機能の錯覚を与えることができたでしょうか。これらの js の設計は、オペレーティング システムの初期の設計と密接に関連しているに違いないと思います。したがって、このレベルでは、オペレーティング システムなどの基本に再び戻ります。実際、現代のテクノロジーを完全に理解できるかどうかは、デザインの歴史を十分に理解しているかどうか、また、当時の各界の達人たちがどのようにして山を越えて巧みに道路を開いたり、川に橋を架けたりしたのかを理解しているかどうかに大きく依存します。資源の枯渇。現代のコンピュータ ハードウェア リソースがどれほど豊富であっても、目標とビジネスの間には一次および二次的な関係があるため、必ず限界があります。制限の中でどのように踊り、創作するかは、歴史を通じて遡ることができる共通の問題です。

    関連する推奨事項:

    JavaScript 非同期プログラミング技術の詳細な説明

    以上がJavaScriptの非同期シングルスレッドの解析(画像とテキスト)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

    声明:
    この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。