初始通道Channel
- Golang语言最有特色的语言类型,与goroutine共同代表Go语言独有的并发编程模式和编程哲学
- 不要使用共享内存来通信,而应该通过同性来共享内存(Rob Pike的至理名言)
- 通道类型的值本身就是并发安全的(Go语言中唯一可以满足并发安全性的类型)
通道的特性
- 对同一通道,发送操作之间是互斥的,接收操作之间也是互斥的
- 发送操作和接收操作中对元素值的处理都是不可分割的
- 发送操作在完全完成之前会被阻塞,接收操作也是如此
- 进入通道并不是接收符右边的元素值,而是它的副本
- 移出通道与上述类似
发送操作和接收操作什么时候可能会长时间阻塞
- 对于缓冲通道, 如果通道已满,那么对它的所有发送操作都会被阻塞,直到通道中有元素值被接收
- 对于缓冲通道,如果通道为空,那么对它的接收操作都会被阻塞,直到通道中有新的元素出现
- 对于非缓冲通道,无论是发送操作还是接收操作,一开始执行都会被阻塞,直到配对的操作也开始执行,才会继续传递
- 非缓冲通道是在用同步的方式传递数据,缓冲通道则是用异步的放肆传递数据
- 由于通道是引用类型,所以它的零值就是nil
- 只声明该类型的变量但没有用make函数对它进行初始化时,该变量的值就会是nil(因此千万不要初始化通道)
哪些操作会引发通道的panic
- 已经初始化,但未关闭的通道,接收和发送操作一定不会引发panic
- 通道一旦关闭,在对它进行发送操作都会引发panic
- 对于nil值的通道,对于它的接收和发送操作都会引发panic
- 为了防止意外的panic,千万不要让接收方关闭通道,而应该让发送方做这件事
package main
import "fmt"
func main() {
ch1 := make(chan int, 2)
// 发送方。
go func() {
for i := 0; i < 10; i++ {
fmt.Printf("Sender: sending element %v...\n", i)
ch1 <- i
}
fmt.Println("Sender: close the channel...")
close(ch1)
}()
// 接收方。
for {
elem, ok := <-ch1
if !ok {
fmt.Println("Receiver: closed channel")
break
}
fmt.Printf("Receiver: received an element: %v\n", elem)
}
fmt.Println("End.")
}
通道的方向
双向通道
即可发也可收的通道
单向通道
- chan <- : 只能用来收
- <- chan :只能用来发
单向通道的主要用途用于约束代码的行为**
- 通过设置接口中函数通道的类型
type Notifier interface { SendInt(ch chan<- int)}
取出通道中的值
使用for range 循环的取出通道中的值
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
for elem := range ch {
fmt.Printf("The element in ch: %v\n", elem)
}
使用 select 语言
- 如果有默认分支,无论通道表达式是否有阻塞,select语句都不会阻塞
- 如果没有默认分支,那么一旦所有的case表达式都没有满足取值条件,那么select语句就会被阻塞
- 如通道关闭了,可以从通道中直接接受一个其元素类型的零值。这时可以使用第二个参数来判断(boolean类型)
func main() {
intChan := make(chan int, 1)
time.AfterFunc(time.Second, func() {
close(intChan)
})
select {
case _, ok := <-intChan:
if !ok {
fmt.Println("The candidate case is closed.")
break
}
fmt.Printf("The candidate case is selected.")
}
}