2016-03-20 28 views
15

GoがノンブロッキングIOをどのように処理するのか多少混乱します。 APIはほとんど私と同期していますが、Goのプレゼンテーションを見るときは、「と呼び出しブロック」のようなコメントを聞くのは珍しいことではありません。ゴーランのブロックとノンブロッキング

ファイルやネットワークから読み取るときにブロックIOを使用しますか? Goルーチンの中から使用するとコードを書き直すような魔法がありますか?

C#の背景から来ていますが、これは非常に非直感的です.C#では非同期APIを使用するときにawaitというキーワードがあります。 これは、APIが現在のスレッドを生成し、後で継続内で続行できることを明確に伝えます。

だからTLDR; Goルーチン内でIOを実行しているときに現在のスレッドをブロックするか、継続を使用して非同期待ち状態マシンのようにC#に変換されますか?

答えて

20

Goには、同期コードを書き込むことができるスケジューラがあり、独自のコンテキスト切り替えを行い、ボンネットの下でasync IOを使用します。したがって、複数のgoroutinesを実行している場合、それらは単一のシステムスレッドで実行される可能性があり、コードがゴルーチンのビューからブロックされている場合、実際にはブロックされません。それは魔法ではありませんが、はい、あなたからすべてのものをマスクします。

スケジューラは、システムスレッドを必要に応じて割り当てます。実際にはブロックされている操作(ファイルIOはブロックされている、またはCコードを呼び出すと思います)に割り当てます。しかし、単純なhttpサーバを使っているなら、実際に少数の "本当のスレッド"を使って何千ものゴルーチンを持つことができます。

あなたがここに行くの内部の仕組みについての詳細を読むことができます:

https://morsmachine.dk/go-scheduler

+2

Go実行時スケジューラ(ネットワーク上のepoll、Windows上のIOCPなど)はネットワークI/Oのシステムコールのみを多重化しています(Go 1.6以下)。ディスク、シリアルなどに衝突するすべてのI/Oシステムコールは、それぞれ単一のOSスレッドを占有します。これが良いか悪いかはGoの開発者のコ​​ミュニティで議論の余地があります。現在のコンセンサスは、一般的な非同期I/Oをユーザーに提供するのがうまくいくと思われますが、実用的な立場からは実際には役に立たないと思われます。 – kostix

+2

... - 1000個のゴルーチンがある場合async I/Oと同時に同じディスクドライブに書き込むことは本当に役に立たないでしょう。専用のライターとバッファードチャネルを使用してください。サイドノート:基盤OSの非同期/ポーラーインタフェースを公開するサードパーティ製のパッケージが存在します。 – kostix

-1

GoがIOまたはシステムコールを実行するときに、現在のゴルーチンをブロックしますが、これが発生したときに、別のゴルーチンが代わりに実行することが許可されていますブロックされたゴルーチン。 Goの古いバージョンでは、一度に1つのgoroutineしか実行できませんでしたが、1.5以降、その数は利用可能なCPUコアの数に変更されています。 (runtime.GOMAXPROCS

Goでブロックすることを心配する必要はありません。たとえば、標準ライブラリのhttpサーバは、ゴルーチンのハンドラ関数を実行します。 HTTPリクエストを処理している間にファイルを読み込もうとするとブロックされますが、最初のブロックがブロックされている間に別のリクエストが来たら、別のゴルーチンが実行してそのリクエストを処理できます。次に、2番目のゴルーチンが終了し、最初のゴルーチンが終了し、最初のゴルーチンがブロックされなくなると、それが再開されます(GOMAXPROCS> 1の場合、ブロックされたゴルーチンは空きスレッドがあればすぐに再開されます)。詳細情報については

、これらをチェックアウト:

11

をあなたが最初@Not_a_Golferの答えを読んで、彼はゴルーチンが予定されているかを理解するために提供されたリンクすべきです。私の答えは、具体的にネットワークI/Oに浸るようなものです。私はGoがどのように協力マルチタスクを達成するのかを理解していると思います。

すべてはゴルーチンで実行され、実際のOSスレッドではないので、コールはブロッキングコールのみを使用し、使用します。彼らは緑色の糸です。だから、IOコールの多くをブロックして、OSスレッドのようなあなたのメモリとCPUをすべて食べることはありません。

ファイルIOは単にsyscallsです。 Not_a_Golferはすでにそれをカバーしていました。 Goは実際のOSスレッドを使用してシステムコールを待機し、ゴルーチンが復帰するとブロックを解除します。 Here UNIX用のファイルreadを見ることができます。

ネットワークIOが異なります。ランタイムは、 "ネットワークポーラー"を使用して、どのゴルーチンがIO呼び出しからブロック解除するべきかを決定します。ターゲットOSによっては、使用可能な非同期APIを使用してネットワークIOイベントを待機します。コールはブロッキングのように見えますが、内部はすべて非同期で処理されます。

たとえば、readをTCPソケットgoroutineで呼び出すと、最初にsyscallを使用して読み取ろうとします。まだ到着していなければ、それはブロックされ、それが再開されるのを待つ。ここでブロックすることは、ゴルーチンを待ち行列に入れて再開するのを待つパーキングを意味します。これは、ネットワークIOを使用すると、ブロックされたgoroutineが他のgoroutinesに実行をもたらす方法です。

func (fd *netFD) Read(p []byte) (n int, err error) { 
    if err := fd.readLock(); err != nil { 
     return 0, err 
    } 
    defer fd.readUnlock() 
    if err := fd.pd.PrepareRead(); err != nil { 
     return 0, err 
    } 
    for { 
     n, err = syscall.Read(fd.sysfd, p) 
     if err != nil { 
      n = 0 
      if err == syscall.EAGAIN { 
       if err = fd.pd.WaitRead(); err == nil { 
        continue 
       } 
      } 
     } 
     err = fd.eofError(n, err) 
     break 
    } 
    if _, ok := err.(syscall.Errno); ok { 
     err = os.NewSyscallError("read", err) 
    } 
    return 
} 

https://golang.org/src/net/fd_unix.go?s=#L237

データが到着するとネットワークポーラーを再開する必要があるゴルーチンを返します。あなたは実行できるゴルーチンを検索するherefindrunnable関数を見ることができます。それはnetpoll関数を呼び出し、これは再開可能なゴルーチンを返します。 kqueueの実装はnetpollhereです。

C#でのasync/waitについては、非同期ネットワークIOは非同期API(WindowsのIO完了ポート)も使用します。何かが到着すると、OSは現在のSynchronizationContextに継続を入れるスレッドプールの完了ポートスレッドの1つでコールバックを実行します。ある意味では、いくつかの類似点があります(パーキング/パーク解除は、継続を呼び出すようなものですが、はるかに低いレベルです)。しかし、これらのモデルは、実装はもちろんのこと、非常に異なります。デフォルトでは、Goroutinesは特定のOSスレッドにバインドされておらず、いずれかのスレッドで再開できますが、問題はありません。対処するUIスレッドはありません。非同期/待機は、具体的には、同じOSスレッドで作業を再開する目的で、SynchronizationContextを使用して行われます。また、緑のスレッドや別のスケジューラがないので、async/awaitは、あなたの関数を複数のコールバックに分割して実行する必要があります。SynchronizationContextは実行されるべきコールバックのキューをチェックする無限ループです。あなたはそれを自分で実装することもできます。本当に簡単です。

+2

私は、ここに "ブロック"という単語に意味的な問題があると思います。もしGoルーチンが結果を出して後で目覚めさせるなら、そのコードの中に何かがなければなりません。継続的な合格スタイルかそのようなもの。いいえ?それはあたかもブロックしているかのように振る舞いますが、場面の裏では実行をもたらし、後で目覚めて続けます。 Goルーチン内で決してforループが終わっていないとしたら、Goルーチンはうまくいきませんし、現在Goルーチンを実行しているスレッドは永遠にブロックされています。 そうでない場合、私はここで完全に混乱しています。 –

+2

最初に@Not_a_Golferの答えと彼が提供したリンクを読み、ゴルーチンのスケジュール方法を理解する必要があります。私の答えは、具体的にはネットワークIOへのダイパーダイブのようなものです。はい、「ブロック」の意味は文脈によって異なります。プログラマーの視点から見ると、ブロックされています。コードがブロックされ、呼び出しが返るまで続行されません。ランタイムの観点からは、実行をもたらす。だからこそ私はそれを駐車場と呼んだ。それは、Goで使われる本当の言葉だ。協調的なマルチタスキングであり、無限ループはゴルーチンとOSスレッドを永遠にブロックしません。 – creker

+0

@RogerAlsingはい、ゴルーチンが「ブロックする」ことを決して行なわず、runtime.Gosched(明示的なスケジューラの利回り)を決して呼び出さない場合、Pを無期限に占有し、他のゴルーチンが実行されないようにします。 – hobbs

関連する問題