Go不推荐用共享内存方式来通信,推荐使用通信的方式来共享内存。
channel用于多个goroutine之间传递数据,且保证整个过程的并发安全性。
channel是Go特有一种数据结构,可以理解为一个管道。
管道分为无缓存的管道和有缓存的管道。
Go语言内存模型规范,对于从无缓冲Channel进行的接收,发生在对该Channel进行的发送完成之前。也就是说,无缓存管道,发送和接收是同步的,任意一个操作都无法离开另一个操作单独存在。否则就会发生deadlock死锁。
package main
import "fmt"
func main() {
done := make(chan int)
var a string
go func(){
a = "hello"
<-done
}()
done <- 0
fmt.Println(a)
}
以上程序Goroutine中的 <-done接收操作和main中的done <- 0发送操作同步进行,管道同步操作为赋值和打印明确了顺序,所以保证了a先被赋值”hello“,然后输出。同理,将发送和接收换位置,程序运行结果一致。
package main
import "fmt"
func main() {
done := make(chan int)
var a string
go func(){
a = "hello"
done <- 0
}()
<-done
fmt.Println(a)
}
对于带缓冲的Channel,对于Channel的第K个接收完成操作发生在第K+C个发送操作完成之前,其中C是Channel的缓存大小。也就是说发送和接收操作可以不同步,当通道中没有要接收的值时,接收动作会阻塞;当通道缓冲区满时发送操作会阻塞。
package main
import "fmt"
func main() {
done := make(chan int, 1) //带缓存
var a string
go func(){
a = "hello"
done <- 1
}()
<-done
fmt.Println(a)
}
因为接收会阻塞等到发送数据完成,所以a先被赋值”hello“,然后输出。此时当我们把发送和接收换位置:
package main
import "fmt"
func main() {
done := make(chan int, 1)
var a string
go func(){
a = "hello"
<-done
}()
done <- 1
fmt.Println(a)
}
运行发现输出为空,这是因为带缓存的通道,在通道缓存还有空间时,发送操作直接完成,所以主程序会先于Goroutine完成退出。
综上,我们其实可以简单的将管道工作理解为:无缓存通道,发送接收需同步;有缓存通道,发送接收可以阻塞等待。
参考:https://zhuanlan.zhihu.com/p/272479809