非零基础自学Golang
文章目录
- 非零基础自学Golang
- 第13章 并发与通道
- 13.3 channel
- 13.3.1 channel类型
- 13.3.2 缓冲机制
第13章 并发与通道
13.3 channel
goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。
引用类型channel是CSP模式的具体体现,用于多个goroutine之间的通信。其内部实现了同步,确保并发安全。
13.3.1 channel类型
channel是一种特殊的类型,和map类似,channel也是一个对应make创建的底层数据结构的引用。
声明一个channel的方式如下:
var 通道变量 chan 通道类型
通道变量是保存通道的引用变量;通道类型是指该通道可传输的数据类型。
当我们复制一个channel或用于函数参数传递时,我们只是拷贝了一个channel引用,因此调用者与被调用者都将引用同一个对象。和其他引用类型一样,channel的零值也是nil。
定义一个channel时,也需要定义发送到channel的值的类型。
channel可以使用内置的make()函数来创建:
make(chan Type) // 等价于make(chan Type, 0)
make(chan Type, capacity)
当capacity为0时,channel是无缓冲阻塞读写的;当capacity大于0时,channel是有缓冲、非阻塞的,直到写满capacity个元素才阻塞写入。
channel通过操作符“<-”来接收和发送数据,接收和发送数据的语法如下:
channel <- value //发送value到channel
<-channel //接收并将其丢弃
x := <-channel //从channel中接收数据,并赋值给x
x, ok := <-channel //同上,并检查通道是否关闭,将此状态赋值给ok
默认情况下,channel接收和发送数据都是阻塞的,除非另一端已准备好接收,这样就使得goroutine的同步更加简单,而不需要显式锁。
举个例子:
[ 动手写 13.3.1]
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
go func() {
fmt.Println(<-ch)
}()
ch <- "test"
time.Sleep(time.Second)
}
动手写13.3.1中定义并创建了一个可以传输string类型的ch通道变量,在匿名协程函数中,从ch通道中接收数据并打印,运行结果如下:
13.3.2 缓冲机制
channel按是否支持缓冲区可分为无缓冲的通道(unbuffered channel)和有缓冲的通道(buffered channel)。
无缓冲的通道是指在接收前没有能力保存任何值的通道。
这种类型的通道要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作。如果两个goroutine没有同时准备好,会导致先执行发送或接收操作的goroutine阻塞等待。
这种对通道进行发送和接收的交互行为本身就是同步的,其中任意一个操作都无法离开另一个操作单独存在。
无缓冲的channel创建格式:
make(chan Type) //等价于make(chan Type, 0)
如果没有指定缓冲区容量,那么该通道就是同步的。
[ 动手写 13.3.2]
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int, 0)
go func() {
for i := 0; i < 3; i++ {
fmt.Printf("len(ch) = %v , cap(ch) = %v\n", len(ch), cap(ch))
ch <- i
}
}()
for i := 0; i < 3; i++ {
time.Sleep(time.Second)
fmt.Println(<-ch)
}
}
动手写13.3.2创建了一个无缓冲通道ch,由于该通道是无缓冲的,因此只有当接收者收到了数据,发送者才能继续发送数据,可以看到运行结果如下:
有缓冲通道是一种在被接收前能存储一个或多个值的通道。
创建一个有缓冲通道的方式如下:
make(chan Type, capacity)
这种类型的通道并不强制要求goroutine之间必须同时完成接收和发送。通道阻塞发送和接收的条件也会不同。只有在通道中没有要接收的值时,接收动作才会阻塞。
只有在通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞。
这导致有缓冲的通道和无缓冲的通道之间有一个很大的不同:无缓冲的通道保证进行发送和接收的goroutine会在同一时间进行数据交换,有缓冲的通道没有这种保证。
[ 动手写 13.3.3]
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int, 3)
go func() {
for i := 0; i < 3; i++ {
fmt.Printf("len(ch) = %v , cap(ch) = %v\n", len(ch), cap(ch))
ch <- i
}
}()
for i := 0; i < 3; i++ {
time.Sleep(time.Second)
fmt.Println(<-ch)
}
}
动手写13.3.3创建了一个容量为3的缓冲channel,由于存在缓冲区,在缓冲区未填满的情况下,程序就不会被阻塞执行。
运行结果如下: