シングルスレッドは Go 言語の機能ではなく、Go 言語はマルチスレッドです。 Golang のスレッド モデルは MPG モデルです。全体的に、Go プロセスとカーネル スレッドは多対多の対応関係にあるため、Go はマルチスレッド モードである必要があります。M スレッドとカーネル スレッドは 1 対 1 に対応し、複数の G は複数の M に対応します。同様に、P はコンテキスト リソースを指します。
このチュートリアルの動作環境: Windows 7 システム、GO バージョン 1.18、Dell G3 コンピューター。
シングルスレッドは Go 言語の機能ではなく、Go 言語はマルチスレッドです。 シングルスレッドだとまだ恥ずかしい、マルチコア時代の高い同時実行性を求めて生まれたと言われている言語なのでしょうか?
Golang のスレッド モデルは MPG モデルですが、一般的に Go プロセスとカーネル スレッドは多対多に対応するため、まずマルチスレッド化する必要があります。このうち、M はカーネル スレッドに 1:1 で対応し、複数の G は複数の M に対応します。P はコンテキスト リソースを指しますが、あまり言うことはありません。 M (またはカーネル スレッド) の数はいつ増加しますか?つまり、現在の M の数では現在の G をすべて移動するようにスケジュールできない場合、新しい M がそれを処理するために使用されます。
Go を 21 世紀の C 言語と比較する人がいますが、それはまず、Go 言語の設計がシンプルだからです。次に、21 世紀で最も人気のある言語ですが、重要なのは並列プログラミングであり、Go は言語レベルから並列処理をサポートしています。
ゴルーチンは、Go の並列設計の中核です。最終的な分析では、ゴルーチンは実際にはスレッドですが、スレッドよりも小さいです。12 個のゴルーチンが下部の 5 つまたは 6 つのスレッドに反映される場合があります。Go 言語は、これらのゴルーチン間のメモリ共有を実現するのに役立ちます。 goroutine の実行に必要なスタック メモリは非常に少なく (約 4 ~ 5KB)、もちろん対応するデータに応じてスケールされます。このため、何千もの同時タスクを同時に実行できます。 Goroutine はスレッドよりも使いやすく、効率的で、軽量です。
Goroutine は Go のランタイムによって管理されるスレッド マネージャーです。 Goroutine は go
キーワードによって実装されますが、これは実際には通常の関数です。
go hello(a, b, c)
キーワード go を使用して goroutine を開始します。例を見てみましょう
package main import ( "fmt" "runtime" ) func say(s string) { for i := 0; i < 5; i++ { runtime.Gosched() fmt.Println(s) } } func main() { go say("world") //开一个新的Goroutines执行 say("hello") //当前Goroutines执行 } // 以上程序执行后将输出: // hello // world // hello // world // hello // world // hello // world // hello
go キーワードを使用すると並行プログラミングを簡単に実装できることがわかります。上記の複数のゴルーチンは同じプロセス内で動作し、メモリデータを共有しますが、共有によって通信するのではなく、通信によって共有するという設計に従う必要があります。
ゴルーチンは同じアドレス空間で実行されるため、共有メモリへのアクセスは同期する必要があります。では、ゴルーチン間でデータを通信するにはどうすればよいでしょうか? Go は優れた通信メカニズム チャネルを提供します。チャネルは、Unix シェルの双方向パイプにたとえることができます。チャネルを通じて値を送受信できます。これらの値は、特定のタイプ (チャネル タイプ) のみにすることができます。チャネルを定義するときは、チャネルに送信される値のタイプも定義する必要があります。チャネルを作成するには make を使用する必要があることに注意してください:runtime.Gosched() は、CPU にタイム スライスを他の時間に譲り、次回のある時点で goroutine の実行を再開し続けることを意味します。
デフォルトでは、スケジューラは単一のスレッドのみを使用します。つまり、同時実行のみが実現されます。マルチコア プロセッサの並列処理を利用するには、プログラム内で runtime.GOMAXPROCS(n) を明示的に呼び出して、複数のスレッドを同時に使用するようにスケジューラに指示する必要があります。 GOMAXPROCS は、ロジック コードを同時に実行できるシステム スレッドの最大数を設定し、前の設定を返します。 n < 1 の場合、現在の設定は変更されません。これは、Go の将来のバージョンでスケジュール設定が改善されると削除される予定です。
ci := make(chan int) cs := make(chan string) cf := make(chan interface{})
<-
ch <- v // 发送v到channel ch. v := <-ch // 从ch中接收数据,并赋值给v
package main import "fmt" func sum(a []int, c chan int) { total := 0 for _, v := range a { total += v } c <- total // send total to c } func main() { a := []int{7, 2, 8, -9, 4, 0} c := make(chan int) go sum(a[:len(a)/2], c) go sum(a[len(a)/2:], c) x, y := <-c, <-c // receive from c fmt.Println(x, y, x + y) }
ch := make(chan type, value) value == 0 ! 无缓冲(阻塞) value > 0 ! 缓冲(非阻塞,直到value 个元素)
我们看一下下面这个例子,你可以在自己本机测试一下,修改相应的value值
package main import "fmt" func main() { c := make(chan int, 2)//修改2为1就报错,修改2为3可以正常运行 c <- 1 c <- 2 fmt.Println(<-c) fmt.Println(<-c) } //修改为1报如下的错误: //fatal error: all goroutines are asleep - deadlock!
上面这个例子中,我们需要读取两次c,这样不是很方便,Go考虑到了这一点,所以也可以通过range,像操作slice或者map一样操作缓存类型的channel,请看下面的例子
package main import ( "fmt" ) func fibonacci(n int, c chan int) { x, y := 1, 1 for i := 0; i < n; i++ { c <- x x, y = y, x + y } close(c) } func main() { c := make(chan int, 10) go fibonacci(cap(c), c) for i := range c { fmt.Println(i) } }
for i := range c
能够不断的读取channel里面的数据,直到该channel被显式的关闭。上面代码我们看到可以显式的关闭channel,生产者通过内置函数close
关闭channel。关闭channel之后就无法再发送任何数据了,在消费方可以通过语法v, ok := <-ch
测试channel是否被关闭。如果ok返回false,那么说明channel已经没有任何数据并且已经被关闭。
记住应该在生产者的地方关闭channel,而不是消费的地方去关闭它,这样容易引起panic
另外记住一点的就是channel不像文件之类的,不需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束range循环之类的
我们上面介绍的都是只有一个channel的情况,那么如果存在多个channel的时候,我们该如何操作呢,Go里面提供了一个关键字select
,通过select
可以监听channel上的数据流动。
select
默认是阻塞的,只有当监听的channel中有发送或接收可以进行时才会运行,当多个channel都准备好的时候,select是随机的选择一个执行的。
package main import "fmt" func fibonacci(c, quit chan int) { x, y := 1, 1 for { select { case c <- x: x, y = y, x + y case <-quit: fmt.Println("quit") return } } } func main() { c := make(chan int) quit := make(chan int) go func() { for i := 0; i < 10; i++ { fmt.Println(<-c) } quit <- 0 }() fibonacci(c, quit) }
在select
里面还有default语法,select
其实就是类似switch的功能,default就是当监听的channel都没有准备好的时候,默认执行的(select不再阻塞等待channel)。
select { case i := <-c: // use i default: // 当c阻塞的时候执行这里 }
有时候会出现goroutine阻塞的情况,那么我们如何避免整个程序进入阻塞的情况呢?我们可以利用select来设置超时,通过如下的方式实现:
func main() { c := make(chan int) o := make(chan bool) go func() { for { select { case v := <- c: println(v) case <- time.After(5 * time.Second): println("timeout") o <- true break } } }() <- o }
runtime包中有几个处理goroutine的函数:
Goexit
退出当前执行的goroutine,但是defer函数还会继续调用
Gosched
让出当前goroutine的执行权限,调度器安排其他等待的任务运行,并在下次某个时候从该位置恢复执行。
NumCPU
返回 CPU 核数量
NumGoroutine
返回正在执行和排队的任务总数
GOMAXPROCS
用来设置可以并行计算的CPU核数的最大值,并返回之前的值。
以上がシングルスレッドは Go 言語の機能ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。