web-dev-qa-db-ja.com

Golangのブロックと非ブロック

Goが非ブロッキングIOを処理する方法については、多少混乱しています。 APIはほとんど私と同期しているように見えます。Goでプレゼンテーションを見るとき、「and the call blocks」のようなコメントを聞くことは珍しくありません

Goはブロッキングを使用していますかIOファイルまたはネットワークから読み取る場合?またはGoルーチン内から使用する場合にコードを書き換える何らかの魔法がありますか?

C#のバックグラウンドから来ると、これは非常に直感的ではないように感じられます。C#では、非同期APIを使用するときにawaitキーワードがあります。これは、APIが現在のスレッドを生成し、後で継続内で継続できることを明確に伝えます。

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

31
Roger Johansson

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

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

Goの内部動作の詳細については、こちらをご覧ください。

https://morsmachine.dk/go-scheduler

32
Not_a_Golfer

最初に@Not_a_Golferの回答と彼が提供したリンクを読んで、ゴルーチンのスケジュール方法を理解する必要があります。私の答えは、ネットワークIO具体的には、ディッパーダイブのようなものです。Goが協調マルチタスクを実現する方法を理解していると思います。

Goはすべてゴロインで実行され、実際のOSスレッドではないため、ブロック呼び出しのみを使用できます。それらは緑色の糸です。したがって、それらの多くをIO呼び出しですべてブロックし、OSスレッドのようにメモリとCPUのすべてを消費することはできません。

File IOは単なるsyscallsです。Not_a_Golferは既にそれをカバーしました。Goはsyscallを待機するために実際のOSスレッドを使用し、戻るときにゴルーチンのブロックを解除します。 Here Unixのファイルreadの実装を確認できます。

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

たとえば、read on TCPソケットゴルーチンは、最初にsyscallを使用して読み取りを試みます。まだ何も到着していない場合は、ブロックして再開されるのを待ちます。ネットワークIOを使用するときに、「ブロックされた」ゴルーチンが他のゴルーチンに実行をもたらす方法です。

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関数を呼び出します。 kqueuenetpoll実装を見つけることができます here

C#のasync/waitについて。 async network IOは非同期API(WindowsのIO完了ポート)も使用します。何かが到着すると、OSはスレッドプールの完了ポートスレッドの1つでコールバックを実行し、現在のSynchronizationContextを継続します。ある意味、いくつかの類似点があります(駐車/駐車解除は継続を呼び出しているように見えますが、はるかに低いレベルにあります)が、これらのモデルは実装はもちろん、非常に異なっています。デフォルトでは、ゴルーチンは特定のOSスレッドにバインドされていません。非同期/待機は、SynchronizationContextを使用して同じOSスレッドでの作業を再開するために特別に作成されています。グリーンスレッドや個別のスケジューラasync/awaitは、SynchronizationContextで実行される複数のコールバックに関数を分割する必要はありません。これは基本的に、実行されるコールバックのキューをチェックする無限ループです。あなた自身でそれを補完し、それは本当に簡単です。

19
creker

Goは、IOまたはsyscallsを実行するときに現在のゴルーチンをブロックしますが、これが発生すると、ブロックされたゴルーチンの代わりに別のゴルーチンを実行できます。 、しかし1.5以降、その数は利用可能なCPUコアの数に変更されました( runtime.GOMAXPROCS

Goでのブロックについて心配する必要はありません。たとえば、標準ライブラリのhttpサーバーは、ゴルーチンでハンドラー関数を実行します。 httpリクエストを処理中にファイルを読み取ろうとするとブロックされますが、最初のリクエストがブロックされている間に別のリクエストが入ると、別のゴルーチンがそのリクエストの実行と処理を許可されます。次に、2番目のゴルーチンが完了し、最初のゴルーチンがブロックされなくなると、再開されます(GOMAXPROCS> 1の場合、フリースレッドがある場合、ブロックされたゴルーチンはさらに早く再開される可能性があります)。

詳細については、以下をご覧ください。

0
user1431317