赞
踩
Don’t communicate by sharing memory, share memory by communicating.
上面这句话是Go语言的主要创造者之一Rob Pike的至理名言,这也体现了Go语言最重要的编程理念。
goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。引用类型channel是CSP模式的具体体现,用于多个goroutine之间的通信,其内部实现了同步,确保并发安全。
channel类型的值本身就是并发安全的,这也是Go语言自带的、唯一一个可以满足并发安全性的类型。
Go语言中channel的关键字是chan,声明、初始化channel的语法如下:
var 通道变量 chan 通道类型
var 通道变量 chan <- 通道类型 // 单向channel,只写
var 通道变量 <- chan 通道类型 // 单向channel,只读
通道变量 := make(chan Type) // 无缓存channel
通道变量 := make(chan Type, 0) // 无缓存channel,与上面等价
通道变量 := make(chan Type, capacity int) // 有缓存channel, capacity > 0
通道变量是保存通道的引用变量;通道类型是指该通道可传输的数据类型。
当capacity为0时,channel是无缓冲阻塞读写的;当capacity大于0时,channel是有缓冲、非阻塞的,直到写满capacity个元素才阻塞写入。
一个通道相当于一个先进先出(FIFO)的队列。也就是说,通道中的各个元素值都是严格地按照发送的顺序排列的,先被发送到通道的元素值一定会先被接收。
元素值的发送和接收都需要用到操作符<-
。我们也可以叫它接送操作符。一个左尖括号紧接着一个减号形象地代表了元素值的传输方向。
ch := make(chan Type, capacity int)
ch <- value // 发送value到ch
<-ch // 从ch中接收一个值并将其丢弃
x := <-ch // 从ch中接收一个数据,并赋值给x
x, ok := <-ch // 同上,并检查通道是否关闭或通道中是否还有值,将此状态赋值给ok
所下示例:
package main
import "fmt"
func main() {
ch := make(chan int, 3)
ch <- 2
ch <- 1
ch <- 3
elem := <-ch
fmt.Printf("The first element received from channel ch: %v\n", elem)
}
运行结果如下:
channel按是否支持缓冲区可分为无缓冲的通道(unbuffered channel)和有缓冲的通道(buffered channel)。
无缓冲的通道是指在接收前没有能力保存任何值的通道。这种类型的通道要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作。如果两个goroutine没有同时准备好,会导致先执行发送或接收操作的goroutine阻塞等待。
这种对通道进行发送和接收的交互行为本身就是同步的,其中任意一个操作都无法离开另一个操作单独存在。
简单无缓冲通道代码示例如下:
package main import ( "fmt" ) func main() { ch := make(chan int) go func() { fmt.Println("进入goroutine") // 添加一个内容后控制台输出:1 true //ch<-1 //关闭ch控制台输出:0 false close(ch) }() c, d := <-ch fmt.Println(c, d) fmt.Println("程序执行结束") }
c, d := <-ch
语句,程序结束时go func还来不及执行。ch <- 1
放到goroutine有效代码最后一行。
有缓冲通道是一种在被接收前能存储一个或多个值的通道。
这种类型的通道并不强制要求goroutine之间必须同时完成接收和发送。通道阻塞发送和接收的条件也会不同。只有在通道中没有要接收的值时,接收动作才会阻塞。只有在通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞。
这导致有缓冲的通道和无缓冲的通道之间有一个很大的不同:无缓冲的通道保证进行发送和接收的goroutine会在同一时间进行数据交换,有缓冲的通道没有这种保证。
由于存在缓冲区,在缓冲区未填满的情况下,程序就不会被阻塞执行。
package main
import "fmt"
func main() {
ch := make(chan int, 3) //缓冲区大小为3,里面消息个数小于等于3时都不会阻塞goroutine
ch <- 1
fmt.Println(<-ch)
ch <- 2
fmt.Println(<-ch)
ch <- 3
ch <- 4
fmt.Println(len(ch))//输出2,表示channel中有两个消息
fmt.Println(cap(ch))//输出3,表示缓存大小总量为3
}
当发送者知道没有更多的值需要发送到channel时,让接收者也能及时知道没有更多的值需要接收是很有必要的,因为这样就可以让接收者停止不必要的等待。
这可以通过内置的close函数和range关键字来实现。
使用close关闭channel时需要注意:
package main import ( "fmt" ) func main() { ch := make(chan int, 3) go func() { for i := 0; i < 3; i++ { fmt.Printf("len = %v, cap = %v\n", len(ch), cap(ch)) ch <- i } close(ch) }() for { if val, ok := <-ch; ok == true { fmt.Println(val) } else { return } } }
发送者在发送完数据后,使用了close关闭channel。channel被关闭后,ok的值就会变为false,最终程序结束运行。运行结果如下:
除了使用上面的方式来遍历channel,还可以使用更加简洁的range关键字。如下所示:
for data := range ch{
fmt.Println(data)
}
当channel关闭后,range也能自动结束本次遍历。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。