给个小建议:如果是初学者,建议把基础知识朗读一遍,有个大概印象,后面思考多了,就会“由量变达到质变”,从而有所顿悟。
// 声明管道,值为nil
var ch chan int
// make创建无缓冲管道和带缓冲管道
ch1 := make(chan string)
ch2 := make(chan string, 10)
func ChanParamRW(ch chan int) {
// 管道可读写
}
func ChanParamR(ch <- chan int) {
// 只能从管道读取数据
}
func ChanParaW(ch chan<- int) {
// 只能向管道写入数据
}
func main() {
ch := make(chan int, 10)
go func() {
for i := 0; i < 20; i++ {
select {
case ch <- 1:
fmt.Println("输入1")
case ch <- 2:
fmt.Println("输入2")
case <-ch:
fmt.Println("输出")
default:
fmt.Println("default")
}
}
}()
time.Sleep(time.Millisecond)
}
输入2
输入2
输入2
输入1
输入2
输入2
输出
输入2
输入2
输入2
输入1
输入1
输出
输入1
输出
输入2
输出
输出
输出
输入1
func main() {
ch := make(chan int, 10)
go func() {
for i := 0; i < 20; i++ {
select {
case ch <- 1:
fmt.Println("输入1")
case ch <- 2:
fmt.Println("输入2")
default:
fmt.Println("default")
}
}
}()
time.Sleep(time.Millisecond)
}
输入2
输入1
输入1
输入1
输入2
输入2
输入2
输入2
输入1
输入1
default
default
default
default
default
default
default
default
default
default
func main() {
ch := make(chan int)
go func() {
for i := 0; i < 20; i++ {
select {
case ch <- 1:
fmt.Println("输入1")
case ch <- 2:
fmt.Println("输入2")
case <-ch:
fmt.Println("输出")
default:
fmt.Println("default")
}
}
}()
time.Sleep(time.Millisecond)
}
default
default
default
default
default
default
default
default
default
default
default
default
default
default
default
default
default
default
default
default
`func main() {
ch := make(chan int)
go func() {
for i := 0; i < 20; i++ {
select {
case ch <- 1:
fmt.Println("输入1")
case ch <- 2:
fmt.Println("输入2")
default:
fmt.Println("default")
}
}
}()
time.Sleep(time.Millisecond)
}
default
default
default
default
default
default
default
default
default
default
default
default
default
default
default
default
default
default
default
default
在runtime包中hchan定义了管道的数据结构,里面有recvq和sendq这两个等待队列。
type hchan struct {
```
recvq waitq // 等待读消息的协程队列
sendq waitq // 等待写消息的协程队列
```
}
处于等待队列中的协程会在其他协程操作管道时被唤醒。
问:为什么说是其他协程呢?
答: 加入到等待队列中的协程应该是阻塞的才对,不管是读操作阻塞还是写操作阻塞,在同一个协程程序中后面的读写操作都是无法执行的,这个时候就只能由其他协程来写或读帮助唤醒这个协程,如果没有其他协程帮忙唤醒,就会出现死锁。
如果没有缓冲区的情况下,在同一个协程内进行写和读,因为没有其他协程去读这个管道,就会一直阻塞写这一步,从而出现死锁;
如果有缓冲区并且缓冲区未满的情况下,因为有缓冲区帮忙缓冲数据,所以在同一个协程内进行读和写是没有问题的。
func main() {
ch := make(chan int)
ch <- 1
fmt.Println(<-ch)
time.Sleep(time.Millisecond)
}
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
/Users/dns/GolandProjects/code/select/demo01.go:10 +0x37
exit status 2
func main() {
ch := make(chan int, 10)
ch <- 1
fmt.Println(<-ch)
time.Sleep(time.Millisecond)
}
1
① 一般情况下,recvq 和 sendq 至少有一个为空(因为对于同一个管道而言,如果有读协程的话,那么等待队列中就不会有写协程,如果是有读有写,协程就不会阻塞,就不会被添加到等待队列中)。
② 特殊情况:同一个协程使用select语句向管道一边写入数据,一边读取数据,此时协程就会分别位于两个等待队列中。
具体来说就是:
var channels = [3]chan int{
nil,
make(chan int),
nil,
}
var numbers = []int{1, 2, 3}
func main() {
time.Sleep(time.Millisecond)
select {
case getChan(0) <- getNumber(0):
fmt.Println("The first candidate case is selected.")
case getChan(1) <- getNumber(1):
fmt.Println("The second candidate case is selected.")
fmt.Println("")
case getChan(2) <- getNumber(2):
fmt.Println("The second candidate case is selected.")
fmt.Println("")
default:
fmt.Println("No candidate case is selected.")
}
}
func getNumber(i int) int {
fmt.Printf("numbers[%d]\n", i)
return numbers[i]
}
func getChan(i int) chan int {
fmt.Printf("channels[%d]\n", i)
return channels[i]
}
channels[0]
numbers[0]
channels[1]
numbers[1]
channels[2]
numbers[2]
No candidate case is selected.