从零开始学Go之并发(二):通道
由于goroutine中资源共享内存,为了避免互斥等问题保证数据正确性,引入通道的概念(channel)
Go 语言中的通道(channel)是一种特殊的类型。在任何时候,同时只能有一个 goroutine 访问通道进行发送和获取数据。goroutine 间通过通道就可以通信。
通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。
声明:
var 通道变量名 chan 类型
var a chan int//一个数据类型为int的通道
chan 类型的空值是 nil,声明后需要配合 make 后才能使用。
创建:
通道是引用类型,需要使用 make 进行创建,格式如下:
通道实例名 := make(chan 类型)
var c2 chan int c1 := make(chan interface{}) c2 = make(chan int)
发送数据:
通道变量 <- 值
c1 := make(chan interface{}) ch <- 0//0放入通道,需要接受否则会报错
把数据往通道中发送时,如果接收方一直都没有接收,那么发送操作将持续阻塞。Go 程序运行时能检查程序发现一些永远无法发送成功的语句并做出提示。
func main() { ch := make(chan interface{}) // 将0放入通道中 ch <- 0 // 将hello字符串放入通道中 ch <- "hello" fmt.Print(ch) }
运行结果:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
D:/SoloWork/Learn/main.go:8 +0x76
接收数据:
通道的收发操作在不同的两个 goroutine 间进行,由于通道的数据在没有接收方处理时,数据发送方会持续阻塞
接收将持续阻塞直到发送方发送数据,如果接收方接收时,通道中没有发送方发送数据,接收方也会发生阻塞,直到发送方发送数据为止
通道一次只能接收一个数据元素。
阻塞接收数据
接收值 := <- 通道名
data := <-ch
非阻塞接收数据
接收值,判断通道是否已经被关闭 := <- 通道名
data, ok := <-ch
当通道被关闭后ok为false
接收任意数据,忽略接收的数据
<-ch
执行该语句时将会发生阻塞,直到接收到数据,但接收到的数据会被忽略。
这个方式主要用来通道在 goroutine 间阻塞收发实现并发同步。
循环接收
通道的数据接收可以借用 for range 语句进行多个元素的接收操作
for data := range ch {}
通道 ch 是可以进行遍历的,遍历的结果就是接收到的数据。数据类型就是通道的数据类型。通过for遍历获得的变量只有一个,即data
关闭通道:
close(通道名)
主要用于终止一个range循环。
只有发送者才能关闭信道,而接收者不能
发送者可通过close()关闭一个信道来表示没有需要发送的值了,接受者通过为接收表达式分配第二个参数来测试信道是否被关闭
单向通道:
var 通道实例 chan<- 元素类型 // 只能发送通道 var 通道实例 <-chan 元素类型 // 只能接收通道
单向通道是一个不能填充数据(发送)只能读取的通道,大多数时候是毫无意义的,主要用于保证代码接口的严谨性
缓冲通道:
将缓冲长度作为第二个参数提供给 make
来初始化一个带缓冲的信道:
通道实例名 := make(chan 类型,缓冲长度)
ch := make(chan int, 100)
当通道有缓冲后,仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞。
带缓冲的通道在发送时无需等待接收方接收即可完成发送过程,并且不会发生阻塞,只有当存储空间满时才会发生阻塞。同理,如果缓冲通道中有数据,接收时将不会发生阻塞,直到通道中没有数据可读时,通道将会再度阻塞。(相当于操作系统里面的互斥初始量)
无缓冲通道可以看作是长度永远为 0 的带缓冲通道(即放入数据则开始阻塞发送,相当于互斥量为1)