文章目录
- Channel(信道)
- 创建不带buffer的信道
- 创建带buffer的信道
- range 和 close
- 参考
Channel(信道)
- 线程安全管道
- 写入一个已经close的channel为导致Panic
创建一个信道
ch1 := make(chan int)
ch2 := make(chan int, 100) // 创建带buffer的信道
创建不带buffer的信道
不会发生数据拷贝,这是因为它不带缓存,所以数据是实时传递的。 因此在写的时候,必须有一个协程阻塞在读上,不然写操作会阻塞。反之亦然。
可以类比与一根水管,水管两端分别对应读端和写端,两端刚开始都是封闭的,当在读端读数据时,由于写端没有数据,所以会阻塞,相当于没有打开水管的另一头,所以没有水进来。当打开放水时(写入数据),这时候读端就能读到数据了。反之先写后读也会在读之前导致写操作阻塞。
eg1:
// 先写后读
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
go func() {
fmt.Println("start write")
ch <- 1 // 这里会阻塞
fmt.Println("end write")
}()
time.Sleep(time.Duration(100) * time.Millisecond)
fmt.Println("start read")
v := <-ch // 从 ch 接收值并赋予 v。
fmt.Println("end read v is ", v)
time.Sleep(time.Duration(100) * time.Millisecond) // 让协程跑完
}
output:
start write
start read
end write
end read v is 1
eg2:
// 先读后写
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
go func() {
fmt.Println("start read")
v := <-ch // 这里会阻塞
fmt.Println("end read v is ", v)
}()
time.Sleep(time.Duration(100) * time.Millisecond)
fmt.Println("start write")
ch <- 1
fmt.Println("end write")
time.Sleep(time.Duration(100) * time.Millisecond) // 让协程跑完
}
output
start read
start write
end write
end read v is 1
创建带buffer的信道
- 当信道的缓冲区填满后,向其发送数据时才会阻塞。
- 当缓冲区为空时,接受方会阻塞。
可以类比为有一定容量的蓄水池,有一个进水口(写端)和一个出水口(读端)
// 如果是不带buffer的,会导致阻塞,进而整个进程阻塞
v := 22
ch := make(chan int, 1)
ch <- v // 将 v 发送至信道 ch。
v = <-ch // 从 ch 接收值并赋予 v。
fmt.Println("v is", v)
range 和 close
发送者可通过 close 关闭一个信道来表示没有需要发送的值了。接收者可以通过为接收表达式分配第二个参数来测试信道是否被关闭:若没有值可以接收且信道已被关闭,那么在执行完
v, ok := <-ch
之后 ok 会被设置为 false。
去接收一个已经被close的channel是被允许的,且会立即返回。
循环 for i := range c 会不断从信道接收值,直到它被关闭。
注意: 只有发送者才能关闭信道,而接收者不能。向一个已经关闭的信道发送数据会引发程序恐慌(panic)。
还要注意: 信道与文件不同,通常情况下无需关闭它们。只有在必须告诉接收者不再有需要发送的值时才有必要关闭,例如终止一个 range 循环。
参考
go指南