コンテンツ
1.チャンネルの紹介
単に関数を同時に実行することは意味がありません。関数の同時実行の意味を反映するために、関数間でデータを交換する必要があります。
共有メモリはデータ交換に使用できますが、共有メモリはさまざまなゴルーチン間で競合状態になる傾向があります。データ交換の正確性を確保するには、メモリをミューテックスでロックする必要があります。これにより、パフォーマンスの問題が発生します。
Goは、共有メモリの代わりに通信方法を使用することを推奨しています。ここでの通信方法は、次の図に示すように、チャネルを使用することです。
地下鉄の駅や食堂、トイレなどの公共の場所に人が多いと、誰もがキューイングの習慣を身につけ、混雑やキューカットによる非効率的なリソースの使用や交換プロセスを回避することを目的としています。コードとデータについても同じことが言えます。データを奪い合うためには、必然的に複数のゴルーチンが実行の非効率につながります。キューを使用する最も効率的な方法は、チャネルがキューのような構造であるということです。
Goのチャンネルは特別なタイプです。一度に1つのゴルーチンのみがチャネルにアクセスしてデータを送受信できます。ゴルーチン間の通信は、チャネルを介して可能です。
チャネルはコンベヤーベルトまたはキューのようなものであり、データの送受信の順序を保証するために常に先入れ先出し(先入れ先出し)ルールに従います。
1.チャネルを宣言します
var 变量 chan 元素类型
var ch1 chan int // 声明一个传递整型的通道
var ch2 chan bool // 声明一个传递布尔型的通道
var ch3 chan []int // 声明一个传递int切片的通道
chanタイプのnull値はnilです。これは、宣言後にmakeで使用する必要があります。
したがって、チャネルはchanintやchanstringなどの1つのタイプのデータのみを送信でき、すべてのタイプをチャネルに使用でき、空のインターフェイスインターフェイス{}も使用できます。チャネルのチャネルを作成することも可能です(時には非常に便利です)。
2.チャネルを作成します
チャネルは参照型であり、makeを使用して作成(割り当てられたメモリ)する必要があります。形式は次のとおりです。
var ch1 chan string
ch1 = make(chan string)
//或者使用短类型
ch1 := make(chan string)
例
ch1 := make(chan int) //创建一个整型类型的通道
ch2 := make(chan interface{}) //创建一个空接口类型的通道, 可以存放任意格式
type Equip struct{ /* 一些字段 */ }
ch2 := make(chan *Equip) //创建Equip指针类型的通道, 可以存放*Equip
2.チャネル操作
チャネルには、送信、受信、および閉じるの3つの操作があります。送信と受信の両方で<-記号を使用します。
チャネルが作成されると、送信および受信操作に使用できます。
チャネルを定義します。
ch := make(chan int)
1.送信
チャネルに値を送信します。
ch <- 10 // 把10发送到ch中
2.受け取る
チャネルから値を受け取ります。
x := <- ch // 从ch中接收值并赋值给变量x
<-ch // 从ch中接收值,忽略结果
3.閉じる
組み込みのclose関数を呼び出してチャネルを閉じます
close(ch)
チャネルを閉じる際の注意点は、すべてのデータが送信されたことが受信者のゴルーチンに通知された場合にのみ、チャネルを閉じる必要があるということです。
ガベージコレクションメカニズムによってチャネルを再利用できます。ファイルを閉じるのとは異なります。終了操作後にファイルを閉じる必要がありますが、チャネルを閉じる必要はありません。
閉じたチャネルには、次の特性があります。
- 閉じたチャネルに値を送信すると、パニックが発生します。
- 閉じたチャネルで受信すると、チャネルが空になるまで値を取得し続けます。(チャネルにまだデータがある場合)
- 閉じていて値がないチャネルでの受信操作は、対応するタイプのゼロ値になります。
- すでに閉じられているチャネルを閉じると、パニックになります。
3.バッファリングされていないチャネル
バッファリングされていないチャネル、別名ブロックされたチャネル
func main() {
ch := make(chan int)
ch <- 10
fmt.Println("发送成功")
}
//这段代码仅作为 描述无缓冲通道,实际会形成deadlock
//具体原因,看下述分析
上記のコードはコンパイルされますが、実行すると次のエラーが発生します。
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
main.go:8 +0x54
上記のコードは、コードch <-10の行をブロックしてデッドロックを形成します。この問題を解決するにはどうすればよいですか?
1つの方法は、値を受け取るためにゴルーチンを開始することです。次に例を示します。
func recv(c chan int) {
ret := <-c
fmt.Println("接收成功", ret)
}
func main() {
ch := make(chan int)
go recv(ch) // 启用goroutine从通道接收值
ch <- 10
fmt.Println("发送成功")
}
ch:= make(chan int)を使用してバッファなしチャネルを作成するため、バッファなしチャネルは、誰かが値を受信した場合にのみ値を送信できます。
バッファリングされていないチャネルの概要:
1)バッファリングされていないチャネルでの送信操作は、別のゴルーチンがチャネルで受信操作を実行するまでブロックされます。この時点で、値は正常に送信され、両方のゴルーチンが実行を継続します。
2)逆に、受信操作が最初に実行された場合、受信者のゴルーチンは、別のゴルーチンがチャネルに値を送信するまでブロックします。
3)バッファリングされていないチャネルを使用した通信により、送信側と受信側のゴルーチンが同期されます。したがって、バッファリングされていないチャネルは同期チャネルとも呼ばれます。
4.バッファリングされたチャネル
上記の問題を解決する別の方法は、バッファリングされたチャネルを使用することです。
make関数でチャネルを初期化するときにチャネルの容量を指定できます
1.バッファチャネル宣言
通道实例 := make(chan 通道类型, 缓冲大小)
func main() {
ch := make(chan int, 1) // 创建一个容量为1的有缓冲区通道
ch <- 10
fmt.Println("发送成功")
}
チャネルの容量がゼロより大きい限り、チャネルはバッファリングされたチャネルであり、チャネルの容量は、チャネルに格納できる要素の数を表します。
2.ブロッキング状態
バッファリングされたチャネルは、多くの特性においてバッファリングされていないチャネルと似ています。バッファリングされていないチャネルは、長さが常にゼロであるバッファリングされたチャネルと考えることができます。したがって、この機能によれば、バッファリングされたチャネルは次の場合でもブロックされます。
- バッファリングされたチャネルがいっぱいになると、データを再送信しようとするとブロックされます。
- バッファリングされたチャネルが空のときにデータを受信しようとするとブロックします。
チャネルの長さを制限し、無限の長さのチャネルを提供しないのはなぜですか?
チャネルは、2つのゴルーチン間の通信の架け橋であることがわかっています。ゴルーチンを使用するコードには、データを提供する1つのパーティと、データを消費する1つのパーティが必要です。データプロバイダーのデータ供給速度がコンシューマーのデータ処理速度よりも速い場合、チャネルが長さを制限しない場合、メモリはアプリケーションがクラッシュするまで拡張し続けます。
したがって、チャネルの長さを制限することは、データプロバイダーの供給速度を制限するのに役立ちます。供給されるデータの量は、データが正常に処理されます。
5.サイクリック読み取りチャネル
上記のコードは、チャネルを1つずつ読み取るのに手間がかかりすぎます。Goを使用すると、範囲を使用してチャネルを読み取ることができます。
func main() {
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
for v := range ch {
fmt.Println(v)
}
}
//deadline
上記のコードを実行すると、チャネルが閉じられるまで範囲が読み取りを終了しないため、デッドロックエラーが報告されます。つまり、バッファチャネルが枯渇すると、範囲が現在のゴルーチンをブロックするため、デッドロックが発生します。次に、この状況を回避しようとします。チャネルが空のときに読み取りを終了することを考える方が簡単です。
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
for v := range ch {
fmt.Println(v)
if len(ch) <= 0 { // 如果现有数据量为0,跳出循环
break
}
}
上記の方法は正常に出力できますが、チャネルアクセスが発生している場合、チャネルサイズを確認する方法ではすべてのデータを取得できないことに注意してください。この例では、データをchに格納するだけで、1つずつ取得します。 。、チャネルサイズが減少しています。別の方法は、チャネルを明示的に閉じることです。
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
// 显式地关闭信道
close(ch)
for v := range ch {
fmt.Println(v)
}
閉じたチャネルはデータの流入を防ぎ、読み取り専用です。閉じたチャネルからデータをフェッチすることはできますが、データを書き込むことはできなくなります。
6.チャネルを閉じます
組み込みのclose()関数を使用してチャネルを閉じることができます(パイプラインが値を格納または取得しない場合は、チャネルを閉じることを忘れないでください)
package main
import "fmt"
func main() {
c := make(chan int)
go func() {
for i := 0; i < 5; i++ {
c <- i
}
close(c)
}()
for {
if data, ok := <-c; ok {
fmt.Println(data)
} else {
break
}
}
fmt.Println("main结束")
}
チャネルが閉じているかどうかを確認しますか?
限られたデータがチャネルを介して送信される場合、閉じる機能でチャネルを閉じることにより、チャネルから値を受信するゴルーチンに待機を停止するように指示できます。
チャネルが閉じている場合、チャネルに値を送信するとパニックが発生し、チャネルから受信した値は常にタイプ0になります。チャネルが閉じているかどうかを判断するにはどうすればよいですか?
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
// 开启goroutine将0~100的数发送到ch1中
go func() {
for i := 0; i < 100; i++ {
ch1 <- i
}
close(ch1)
}()
// 开启goroutine从ch1中接收值,并将该值的平方发送到ch2中
go func() {
for {
// 通道关闭后再取值ok=false
i, ok := <-ch1
if !ok {
break
}
ch2 <- i * i
}
close(ch2)
}()
// 在主goroutine中从ch2中接收值打印
for i := range ch2 { // 通道关闭后会退出for range循环
fmt.Println(i)
}
}
説明:チャネルのいくつかのブロッキング条件を知った後、デッドロックを防ぐために、チャネルからデータを読み取るためのより使いやすい方法を使用できます
if i, ok := <-ch1 ;ok{
...
}
7つの片道チャネル
複数のタスク関数間でパラメーターとしてチャネルを渡す場合があります。また、関数でチャネルを送信のみまたは受信のみに制限するなど、さまざまなタスク関数でチャネルを使用して制限する場合もあります。
var 通道实例 chan<- 元素类型 // 只能发送通道
var 通道实例 <-chan 元素类型 // 只能接收通道
//往通道中写
func counter(out chan<- int) {
for i := 0; i < 100; i++ {
out <- i
}
close(out)
}
func squarer(out chan<- int, in <-chan int) {
for i := range in {
out <- i * i
}
close(out)
}
//从通道中读
func printer(in <-chan int) {
for i := range in {
fmt.Println(i)
}
}
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go counter(ch1)
go squarer(ch2, ch1)
printer(ch2)
}