非同期プログラミングとマルチスレッドの違いは何ですか?


234

私はそれらは基本的に同じものだと思っていました—プロセッサー間でタスクを分割するプログラムを書く(2つ以上のプロセッサーを持つマシン上で)。それから私はこれを読んでいます、それは言う:

非同期メソッドは、非ブロッキング操作を目的としています。非同期メソッドのawait式は、待機中のタスクの実行中に現在のスレッドをブロックしません。代わりに、式は継続としてメソッドの残りの部分にサインアップし、asyncメソッドの呼び出し元に制御を返します。

asyncおよびawaitキーワードを使用しても、追加のスレッドが作成されることはありません。非同期メソッドは独自のスレッドで実行されないため、非同期メソッドはマルチスレッドを必要としません。メソッドは現在の同期コンテキストで実行され、メソッドがアクティブな場合にのみスレッドで時間を使用します。Task.Runを使用してCPUにバインドされた作業をバックグラウンドスレッドに移動できますが、バックグラウンドスレッドは、結果が利用可能になるのを待っているだけのプロセスには役立ちません。

そして、誰かがそれを英語に翻訳できるかどうか疑問に思っています。これは、非同期性(つまり、単語ですか?)とスレッド化を区別しているようで、非同期タスクはあるがマルチスレッド化はできないプログラムを作成できることを示しています。

これで、pgの例のような非同期タスクの考え方がわかりました。ジョンスキートのC#の詳細、第3版の467

async void DisplayWebsiteLength ( object sender, EventArgs e )
{
    label.Text = "Fetching ...";
    using ( HttpClient client = new HttpClient() )
    {
        Task<string> task = client.GetStringAsync("http://csharpindepth.com");
        string text = await task;
        label.Text = text.Length.ToString();
    }
}

asyncキーワードの手段が、「この機能は、それが呼ばれるたびに、そのコールが呼ばれるようにした後、その完了がすべてのために必要とされるコンテキストで呼び出されることはありません。」

つまり、あるタスクの途中でそれを書く

int x = 5; 
DisplayWebsiteLength();
double y = Math.Pow((double)x,2000.0);

以来、DisplayWebsiteLength()とは何の関係も持っていないxかをy、原因となりますDisplayWebsiteLength()ように、「バックグラウンドで」実行されます

                processor 1                |      processor 2
-------------------------------------------------------------------
int x = 5;                                 |  DisplayWebsiteLength()
double y = Math.Pow((double)x,2000.0);     |

明らかにそれは愚かな例ですが、私は正しいのですか、それとも完全に混乱していますか?

(また、私は理由について困惑しているsendere、これまで以上の関数の本体では使用されません。)


13
これは素晴らしい説明です:blog.stephencleary.com/2013/11/there-is-no-thread.html
Jakub Lortz

senderそしてeほとんど唯一の場所-これは実際にイベントハンドラで示唆されているasync voidことが望ましいです。ほとんどの場合、これはボタンのクリックなどで呼び出されます。その結果、このアクションはアプリケーションの残りの部分に対して完全に非同期で発生します。しかし、それはすべて1つのスレッド、つまりUIスレッド(UICPスレッドにコールバックをポストするIOCPスレッドのごくわずかな時間)にあります。
Luaan


3
DisplayWebsiteLengthコードサンプルに関する非常に重要な注意:ステートメントでは使用HttpClientしないでusingください-負荷が大きい場合、コードは使用可能なソケットの数を使い果たし、SocketExceptionエラーが発生する可能性があります。不適切なインスタンス化に関する詳細情報。
Gan

1
@JakubLortz記事の対象が本当にわからない。スレッド、割り込み、CPU関連のものなどについての十分な知識が必要なため、初心者向けではありません。上級ユーザー向けではありません。それらはすべて明確になっているためです。私はそれが誰かがそれがすべてについて何であるかを理解するのを助けないだろうと確信しています-高すぎる抽象化です。
ロレノ2018年

回答:


589

あなたの誤解は非常に一般的です。多くの人々は、マルチスレッドと非同期は同じものであると教えられていますが、そうではありません。

類推は通常役立ちます。あなたはレストランで料理しています。卵とトーストの注文があります。

  • 同期:卵を調理してからトーストを調理します。
  • 非同期、シングルスレッド:卵の調理を開始し、タイマーを設定します。トーストクッキングを開始し、タイマーを設定します。両方が調理している間、あなたは台所を掃除します。タイマーがオフになると、卵を火から外し、トースターからトーストを出して提供します。
  • 非同期、マルチスレッド:もう2人のコックを雇います。1人は卵を調理し、もう1人はトーストを調理します。リソースを共有するときにキッチンで料理人が互いに衝突しないように料理人を調整するという問題があります。そして、あなたはそれらを支払う必要があります。

さて、マルチスレッディングが非同期の1種類にすぎないことは理にかなっていますか? スレッディングはワーカーに関するものです。非同期性はタスクに関するものです。マルチスレッドワークフローでは、タスクをワーカーに割り当てます。非同期シングルスレッドワークフローでは、タスクのグラフがあり、一部のタスクは他のタスクの結果に依存しています。各タスクが完了すると、完了したばかりのタスクの結果から、実行可能な次のタスクをスケジュールするコードが呼び出されます。ただし、(うまくいけば)すべてのタスクを実行するのに必要なワーカーは1人だけで、タスクごとに1人ではありません。

多くのタスクがプロセッサに依存しないことを理解するのに役立ちます。プロセッサにバインドされたタスクの場合、プロセッサの数と同じ数のワーカー(スレッド)を採用し、各ワーカーに1つのタスクを割り当て、各ワーカーに1つのプロセッサを割り当て、各プロセッサに結果を計算する以外の処理を行わせることが理にかなっています。できるだけ早く。ただし、プロセッサで待機していないタスクの場合、ワーカーを割り当てる必要はありません。結果が利用可能であるというメッセージが到着するのを待って、待っている間に何か他のことをします。そのメッセージが届いたら、完了したタスクの継続を次のチェックリストとしてスケジュールすることができます。

それでは、ジョンの例をさらに詳しく見てみましょう。何が起こるのですか?

  • 誰かがDisplayWebSiteLengthを呼び出します。WHO?気にしません。
  • ラベルを設定し、クライアントを作成し、何かをフェッチするようクライアントに要求します。クライアントは、何かをフェッチするタスクを表すオブジェクトを返します。そのタスクは進行中です。
  • 別のスレッドで進行中ですか?おそらく違います。スレッドがない理由についてのスティーブンの記事を読んでください。
  • 今、私たちはタスクを待ちます。何が起こるのですか?タスクを作成してから待機するまでの間にタスクが完了したかどうかを確認します。はいの場合、結果をフェッチして実行を続けます。それが完了していないとしましょう。 このメソッドの残りの部分をそのタスクの継続として登録し、戻ります。
  • これで、制御が呼び出し元に戻りました。それは何をするためのものか?それが望むものは何でも。
  • 次に、タスクが完了したとします。それはどのようにしたのですか?多分それは別のスレッドで実行されていたのかもしれませんし、現在のスレッドで実行が完了するまで許可した呼び出し元の可能性もあります。とにかく、これでタスクが完了しました。
  • 完了したタスクは、正しいスレッド(おそらく唯一のスレッド)に、タスクの継続を実行するように要求します。
  • コントロールは、待機の時点で残したメソッドにすぐに戻ります。今そこにある私たちが割り当てることができるので、その結果利用可能textとする方法の残りの部分を実行します。

それはちょうど私の例えのようです。誰かがあなたに文書を求めます。書類をメールで送り、他の作業を続けます。メールで届いたときに通知が届き、気になったらワークフローの残りの作業を行います。封筒を開いて、配送料などを支払います。すべてを行うために別の労働者を雇う必要はありません。


8
@ user5648283:ハードウェアはタスクについて考えるのに不適切なレベルです。タスクは単に、(1)将来値が使用可能になることを表し、(2)その値が使用可能になったときに(正しいスレッドで)コードを実行できるオブジェクトです。個々のタスクが将来どのように結果を取得するかは、それ次第です。「ディスク」や「ネットワークカード」などの特別なハードウェアを使用してこれを行うものもあります。CPUなどのハードウェアを使用するものもあります。
Eric Lippert、2016年

13
@ user5648283:もう一度、私のアナロジーについて考えてみましょう。誰かが卵とトーストを調理するように頼んだら、特別なハードウェア(ストーブとトースター)を使用し、ハードウェアが機能している間にキッチンを掃除できます。誰かが卵、トースト、最後のホビットムービーの元の批評を求めてきたら、卵とトーストが調理されている間にレビューを書くことができますが、そのためのハードウェアを使用する必要はありません。
Eric Lippert、2016年

9
@ user5648283:「コードの再配置」についての質問は、これを考慮してください。利回りを返すメソッドPと、Pの結果に対してforeachを実行するメソッドQがあるとします。コードをステップ実行します。Qを少し実行してから、Pを少し実行してから、Qを少し実行しているのがわかります。その意味がわかりますか? 待っているのは、基本的にデザインの凝った服の収益率です。今はもっとはっきりしていますか?
Eric Lippert

10
トースターはハードウェアです。ハードウェアはそれを処理するためにスレッドを必要としません。ディスクやネットワークカードなど、OSスレッドのレベルよりもはるかに低いレベルでは動作しません。
エリックリッペルト2017年

5
@ShivprasadKoirala:それは絶対に真実ではありません。あなたがそれを信じるなら、あなたは非同期性についていくつかの非常に誤った信念を持っています。C#の非同期の全体のポイントは、スレッドを作成しないことです。
Eric Lippert

27

ブラウザ内のJavascriptは、スレッドを持たない非同期プログラムの良い例です。

複数のコードが同じオブジェクトに同時に触れることを心配する必要はありません。各関数は、他のJavaScriptがページで実行される前に実行を終了します。

ただし、AJAXリクエストのようなものを実行すると、コードはまったく実行されないため、他のJavaScriptは、そのリクエストが返され、それに関連付けられたコールバックを呼び出すまで、クリックイベントなどに応答できます。AJAX要求が戻ったときにこれらの他のイベントハンドラーの1つがまだ実行中の場合、ハンドラーは、完了するまで呼び出されません。実行しているJavaScriptの「スレッド」は1つだけですが、必要な情報が得られるまで、実行していたことを効果的に一時停止することは可能です。

C#アプリケーションでは、UI要素を処理しているときはいつでも同じことが起こります。UIスレッドを操作しているときのみ、UI要素との対話が許可されます。ユーザーがボタンをクリックし、ディスクから大きなファイルを読み取って応答したい場合、経験の浅いプログラマーがクリックイベントハンドラー内でファイルを読み取るのを誤る可能性があり、アプリケーションがスレッドが解放されるまで、クリック、ホバリング、またはその他のUI関連のイベントへの応答が許可されないため、ファイルの読み込みが完了しました。

プログラマーがこの問題を回避するために使用できるオプションの1つは、ファイルを読み込む新しいスレッドを作成し、そのスレッドのコードに、ファイルが読み込まれたときに、UI要素で更新できるように、UIスレッドで残りのコードを再度実行する必要があることを伝えることです。それはファイルで見つけたものに基づいています。最近まで、このアプローチはC#ライブラリと言語によって簡単に作成できるため、非常に人気がありましたが、基本的にはそれよりも複雑です。

ハードウェアとオペレーティングシステムのレベルでファイルを読み取るときにCPUが何をしているのかを考えると、基本的には、ディスクからメモリにデータの一部を読み取り、オペレーティングシステムに「割り込み」を与える命令を発行しています。 "読み取りが完了したとき。つまり、ディスク(または実際のI / O)からの読み取りは、本質的に非同期の操作です。そのI / Oが完了するのを待機するスレッドの概念は、ライブラリ開発者がプロ​​グラミングを容易にするために作成した抽象化です。それは必要はありません。

これで、.NETのほとんどのI / O操作には、...Async()呼び出すことができる対応するメソッドがあり、Taskほぼ即座に戻ります。これTaskにコールバックを追加して、非同期操作の完了時に実行するコードを指定できます。また、そのコードを実行するスレッドを指定することもできます。非同期タスクが非同期タスクをキャンセルすることを決定したかどうかを随時確認できるトークンを提供して、そのタスクをすばやく停止する機会を与えることができますそして優雅に。

async/awaitキーワードが追加されるまで、C#はコールバックコードがどのように呼び出されるかについてより明確でした。これらのコールバックは、タスクに関連付けられたデリゲートの形式であったためです。...Async()コードの複雑さを回避しながら、操作を使用する利点を引き続き提供するためにasync/await、これらのデリゲートの作成を抽象化します。しかし、コンパイルされたコードにはまだ残っています。

そのため、UIイベントハンドラーawaitをI / O操作にして、UIスレッドを解放して他のことを行うことができ、ファイルの読み取りが完了すると、多かれ少なかれ自動的にUIスレッドに戻ることができます。新しいスレッドを作成します。


実行中のJavaScriptの「スレッド」は1つだけです-Webワーカーでは当てはまりません。
oleksii

6
@oleksii:それは技術的には真実ですが、Web Workers API自体は非同期であり、Web Workersは、呼び出されたWebページのJavaScript値またはDOMに直接影響を与えることができないため、それについては説明しませんでしたから、これはこの回答の重要な2番目の段落がまだ当てはまることを意味します。プログラマーの観点から見ると、Webワーカーの呼び出しとAJAXリクエストの呼び出しにはほとんど違いがありません。
StriplingWarrior
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.