web-dev-qa-db-ja.com

非同期関数が本当に非同期かどうかはどのようにしてわかりますか?

私は node.js-style非同期 の方法で関数を書いていますが、これらの関数が本当に非同期であるかどうか、つまり、それらが並列ではなく順次に実行されているかどうかをどうやって知ることができますか?

これが私のコードです(詳細を削除するように変更されています):

_// write the async function
function compileData(data, callback) {
  try {
    // ... do all the work ...
    callback(null, result); // instead of `return result;`, pass it to the callback
  } catch (err) {
    callback(err, null);
  }
}

// use the async function
compileData(mydata1, function (err, data) {
  if (err) console.error(err);
  else // do something with compiled `data`
});
compileData(mydata2, function (err, data) {
  if (err) console.error(err);
  else // do something with compiled `data`
});
_

上記のように非同期関数を使用する場合、それらが並行して実行されていることをどのように確認できますか? compileData()の2番目の呼び出しが、最初の呼び出しが完了するまで開始を待たないことをどのようにして知ることができますか?

5
chharvey

非同期と並列という用語はしばしば交換されますが、非同期とは、実行のメインフローに関連して帯域外で何かが発生すること(つまり、実行のメインフローが中断されること)を意味します。したがって、非同期は(潜在的に任意のタイミングで)メインスレッドの中断を意味し、必ずしも完全な2番目のスレッドでは中断されません。一部のシステム(JavaScriptは除く)では、メインスレッドが一時的に中断され、ハイジャックされてI/O完了ルーチンを処理します(その後、中断された作業が再開されます)。

一方、並列とは、2つ以上のこと(スレッド)が同時に発生することを意味します。ユニプロセッサでは、真の並列実行はありません。ただし、CPU時間を別のスレッドに与えるために、現在実行中のスレッドに(多くの場合、任意の時点で)割り込みをかけることでシミュレートされます。マルチプロセッサシステムでは、異なるCPUによる同時実行と、スレッドの実行をインターリーブしてCPUより多くのスレッドを実行するための割り込みの両方が行われます。

(もしそうなら、非同期は(疑似)並列処理の軽量形式であり、1つはメインスレッドで、もう1つは必ずしも完全な本格的なスレッドではなく、I/O完了ルーチンです。)

JavaScriptは本質的にシングルスレッドであることにも注意してください。つまり、マルチプロセッサチップ上でも真の並列実行はありませんが、C、C++ではJavaまたはC#では、マルチプロセッサチップ上で本当に同時にスレッドを実行できます。JavaScriptにはスレッドの概念はありませんが、コールバックはあります。

他のルーチンに提供するコールバック(関数参照)は、同期的に実行される場合と、非同期的に実行される場合があります。それは、それらのコールバックがどのように使用されるかに依存します。コールバックが渡されるコードからは(コールバックが提供されるライブラリルーチンを知らなければ)実際にはわかりません。したがって、コールバックが非同期実行を即座に意味するわけではありません。

(コールバックは、関数参照が実際にゼロ回以上使用される可能性があるという点でさらに複雑です。コールバックがいつまたは何回使用されるかに関して、言語のセマンティクスから本質的に保証はありません。)

あなたの例では、コールバックはメインスレッドに対して同期して、そしてメインスレッドの直接制御下で実行されているように見えます。

I/O(またはタイムアウト)完了ルーチンの結果として、JavaScriptで非同期実行が発生します。 JavaScriptは本質的にシングルスレッドであるため、これが機能する方法は、(1つの)メインスレッドが100%の完了まで実行され、呼び出し元(JavaScriptスケジューラ)に戻ることができるということです。この時点では、メインスレッドは実行されておらず、JavaScriptスケジューラはそのメインスレッドを使用して完了ルーチンを呼び出すことができます。複数のI/O要求が複数の完了ルーチンをトリガーする場合、それらはそれぞれ、インタリーブなしで、スケジュールされた順序(I/O完了の順序)で次々に100%完了まで実行されます。

(もちろん、I/O完了ルーチン自体が追加のI/O呼び出しを行って、後続のI/O完了コールバックを渡します。その完了ルーチンのセットの全体的な実行は、上記のような他のI/O完了コールバックとインターリーブされます。 。)

あなたの場合、コールバックは関数参照の呼び出しによって実行され、メインスレッドの通常の制御下で実行されます。メカニズムは間接的な関数呼び出しであるため、コールバックは標準の関数呼び出しメカニズムを使用して呼び出されます。メインスレッドは最後まで実行されません。コールバックの実行はスケジュールされていませんが、直接呼び出されます。違いを確認するには、次のように置き換えます。

callback(null, result);

setTimeout(callback,100,null,result);

これによりコールバックも呼び出されますが、現在実行中のルーチン(実際にはスレッド全体)が呼び出し元に戻るまでしばらく待機します。

3
Erik Eidt

非同期とは、(通常)同期コードの「正確にthisの順序」とは異なり、単に「特定の順序ではない」という意味です。

並列実行は、どのプロセス|スレッド|タスクがどのコア|ワーカーで実行されるかを指定しないため、非同期であることを意味します。つまり、コードはsafeであるため、相対的な順序に依存したり、仮定したりすることはできません。共有値は変更されません。

保留中のタスクのキューを維持するシングルコアマシンのスケジューラを想像してみてください。そのスケジューラーはプログラムを実行しますあたかもそれは同期的でした。

2
Caleth

定義を混同していますが、非同期とは、関数が呼び出された直後にコードフローをブロックせずに戻ることを意味します...後で戻り値を取得できます。それは、やかんで水を沸騰させるようなもので、水を入れて、何か他のことをするために立ち去ります。湯沸かし器は、水が熱くなると信号を送ります。

Paralellは、コードを複数のCPUコアで同時に実行できることを意味します。つまり、2つ以上のやかんに水を入れ、一度にそれらをオンにすると、それが並列になります。

やかんが2つあり、水が沸騰するまでそのままにして非同期にしておきます。やかんの上に手を置く必要がある場合は、並行で同期しています。

したがって、nodejsに到達すると、コードはシングルスレッドなので、決して並列処理されることはありません。あなたは他の言語を見る必要があります。並列処理が必要な場合はgolang。

Nodejsは非同期のみです...したがって、PI番号を同時に計算する2つの関数を使用できますが、それらは単一のCPUコアでのみ実行されます...したがって、表面上は2つの関数が「実行中」ですが、人はいつでも「仕事」をしている。

ノードで新しいプロセスを開始できるようにするダーティなハックがありますが、それはあまりにも悪いので、実際には何も考慮しないでください。そして、nodejsの設計は非常に悪いので、正気なプログラミング慣行を忌み嫌います。文字列が100行を超えるコードは、判読できません。エラー処理は設計上問題がありますが、同じことを行うように設計されているため、golangを調べる必要があります。使用は簡単ですが、すべてが機能し、正しく設計されています。

Golangでは、非同期関数を作成するために、その前に "go"キーワードを追加するだけです。これですべてです。 「a()」は同期的で、「go a()」は非同期的です。そして、最高のもの...「go a();」を呼び出すと複数回、自動的に非同期かつ並列になります。

意図的に壊れたものを使用して、自分を殺して時間を無駄にする必要はありません。私はそれが厳しい判断であることを知っており、多くの人々がこれに多くの時間を費やしました...しかし、本当に私がまだノードを使用している人には驚いています...それは8年前は他に選択肢がなかったので良かったです。全体としては、汚いハックであるIMOです。これは、PHPと比較してかつてはすばらしいものでした... 8年前、いくつかの非常に特殊なユースケースでは。

2
Slawek

コード例は並列でも非同期でもありません。

この例のコードでは、同期フロー制御にコールバック関数を使用しています。

非同期

非同期にしたい場合は、他の作業を続けながら、制御を生成する何かを行う必要があります。典型的な例は...

  • データベースを呼び出すか、REST関数がすぐに完了し、完了時にコールバックを呼び出す。
  • 現在の作業が完了するまで実行を延期するには、setTimeout()またはsetInterval()を使用します。

あなたの例では、それに非同期のものを追加する必要があります、と言う

function compileData(data, callback) {
    try {
        // databaseRequest is an asynchronous call that executes immediately.
        // When the request is complete, the library calls the anonymous function, 
        // which calls your callback.
        databaseRequest(data,function(result) {
            callback(null, result);
        });
    } catch (err) {
        callback(err, null);
    }
}

これはおそらくあなたが意図したことですが、要点を明確にするために、このような場合、呼び出しはすぐに実行を再開して続行し、結果は無名関数を介して後で返されるため、非同期であることがわかります。

確信が持てない場合は、console.log()ステートメントをいくつか記述し、それらが実行される順序を確認してください。

平行

問題のコードは、ノード環境がシングルスレッドであるため、並列に関数を実行していません。それらを並行して実行したい場合は、別のNodeプロセスを起動するか、この目的のためにツールセットの1つを使用する必要があります。これは推奨されません。

2
joshp