41

Go の同時実行性を理解しようとしています。特に、私はこのスレッドセーフでないプログラムを書きました:

package main

import "fmt"

var x = 1

func inc_x() { //test
  for {
    x += 1
  }
}

func main() {
  go inc_x()
  for {
    fmt.Println(x)
  }
}

との競合状態を防ぐためにチャネルを使用する必要があることは認識していますがx、それはここでのポイントではありません。プログラムは印刷1され、それから永遠にループしているように見えます (それ以上何も印刷せずに)。番号の無限のリストを出力することを期待します。競合状態のために一部をスキップして他のものを繰り返す可能性があります (またはさらに悪いことに、 で更新されている間に番号を出力しますinc_x)。

私の質問は: プログラムが 1 行しか出力しないのはなぜですか?

明確にするために: このおもちゃの例では、意図的にチャネルを使用していません。

4

4 に答える 4

40

Go のgoroutinesについて留意すべき点がいくつかあります。

  1. それらは、Java や C++ のスレッドの意味でのスレッドではありません。
    • ゴルーチンはグリーンレットに似ています
  2. go ランタイムは、システム スレッド間でゴルーチンを多重化します。
    • システム スレッドの数は環境変数によって制御され、GOMAXPROCS現在はデフォルトで 1 になっていると思います。これは将来変更される可能性があります
  3. ゴルーチンが現在のスレッドに戻る方法は、いくつかの異なる構造によって制御されます
    • select ステートメントは制御をスレッドに戻すことができます
    • チャネルで送信すると、制御をスレッドに戻すことができます
    • IO操作を行うと、制御をスレッドに戻すことができます
    • runtime.Gosched()制御を明示的にスレッドに戻します

表示されている動作は、メイン関数がスレッドに返されず、代わりにビジー ループに関与し、スレッドが 1 つしかないため、メイン ループが実行される場所がないためです。

于 2012-04-10T21:24:14.563 に答える
18

thisおよびthisによると、一部の呼び出しは、CPU バウンドのゴルーチン中に呼び出すことができません (ゴルーチンがスケジューラーに渡らない場合)。これにより、他のゴルーチンがメインスレッドをブロックする必要がある場合にハングアップする可能性があります ( でwrite()使用される syscallの場合などfmt.Println()) 。

私が見つけた解決策は、次のように、CPU バウンド スレッドを呼び出しruntime.Gosched()てスケジューラに戻すことでした。

package main

import (
  "fmt"
  "runtime"
)

var x = 1

func inc_x() {
  for {
    x += 1
    runtime.Gosched()
  }
}

func main() {
  go inc_x()
  for {
    fmt.Println(x)
  }
}

ゴルーチンで 1 つの操作しか実行していないため、runtime.Gosched()が頻繁に呼び出されています。init の呼び出しruntime.GOMAXPROCS(2)は桁違いに高速ですが、数値をインクリメントするよりも複雑なことを行う場合 (たとえば、配列、構造体、マップなどを扱う場合) は、非常にスレッドセーフではありません。

その場合のベスト プラクティスは、チャネルを使用してリソースへの共有アクセスを管理することです。

更新: Go 1.2 以降、インライン化されていない関数呼び出しでスケジューラを呼び出すことができます。

于 2012-04-10T20:56:57.010 に答える
8

それは2つのものの相互作用です。1 つは、Go はデフォルトで単一のコアのみを使用し、2 つは Go がゴルーチンを協調的にスケジュールする必要があります。関数 inc_x は生成されないため、使用されている単一のコアを独占します。これらの条件のいずれかを緩和すると、期待どおりの出力が得られます。

「コア」と言うのはちょっとグロスです。Go は実際にはバックグラウンドで複数のコアを使用する場合がありますが、GOMAXPROCS という変数を使用して、システム以外のタスクを実行するゴルーチンをスケジュールするスレッドの数を決定します。FAQEffective Goで説明されているように、デフォルトは1ですが、環境変数またはランタイム関数でより高く設定することができます. これにより、期待どおりの出力が得られる可能性がありますが、プロセッサに複数のコアがある場合のみです。

コアや GOMAXPROCS とは独立して、ランタイムのゴルーチン スケジューラにその仕事をする機会を与えることができます。スケジューラーは実行中のゴルーチンを横取りすることはできませんが、ゴルーチンがランタイムに戻って IO、time.Sleep()、runtime.Gosched() などのサービスを要求するまで待機する必要があります。このようなものを inc_x に追加すると、期待される出力が生成されます。main() を実行している goroutine はすでに fmt.Println を使用してサービスを要求しているため、2 つの goroutine が定期的にランタイムに譲ることで、ある種の公正なスケジューリングを行うことができます。

于 2012-04-10T23:37:59.077 に答える
2

確かではありませんが、CPUを占有していると思います。inc_xIO がないため、制御は解放されません。

それを解決する2つのことを見つけました。1 つは、プログラムの開始時に呼び出すruntime.GOMAXPROCS(2)ことでした。その後、gorouting を提供する 2 つのスレッドがあるため、機能します。もう1つはtime.Sleep(1)、インクリメント後に挿入することxです。

于 2012-04-10T20:37:01.567 に答える