可以使用全局变量加锁同步来解决goroutine的通讯,但并不完美,这就引出了新的通讯机制-channel
channel的基本概念
channel是Go语言中的一种类型,用于在协程之间传递数据。
- channel的本质是一种数据结构——队列,数据是FIFO先进先出的。
- 线程安全,多协程访问时,不需要加锁,channel本身是线程安全的
- channel是有类型的,一个string的channel只能存放string类型的数据
ch := make(chan string)
这样数据就可以从一个协程中创建并流入管道,再从管道流出到另一个协程中。通过这样的方式在协程之间实现通信。
channel的使用方法
- channel是引用类型
- channel必须初始化后才能写数据,即make之·后才能使用
package main
import "fmt"
func main() {
// 创建一个可以存放3个int类型的管道
intChan := make(chan int, 3)
fmt.Println("intChan的值:", intChan, " intChan的地址", &intChan)
// 向管道内写入数据
intChan <- 10
num := 985
intChan <- num
// 查看管道的长度和cap容量
fmt.Println("channel len: ", len(intChan), " cap: ", cap(intChan))
// 从管道中读取数据
num2 := <-intChan
fmt.Println("num2: ", num2)
fmt.Println("channel len: ", len(intChan), " cap: ", cap(intChan))
}
channel的容量是有限制的,不能动态增长,是静态的,如果超过这个容量会报deadlock错误。当管道内的数据取完之后,再对空管道进行取数操作也会报deadlock错误。
也可以直接让元素出管道,不用变量接就好
<-intChan
channel的关闭
使用内置函数close可以关闭channel,当channel关闭后,就不能再向channel写数据了,但是仍然可以从channel中读取数据
package main
func main() {
intChan := make(chan int, 3)
intChan <- 100
intChan <- 200
close(intChan) //close之后就不能再写数据到channel中
intChan <- 300
}
当channel关闭后,继续向其中写入数据会导致panic
channel的遍历
channel支持for-range的方式进行遍历
- 在遍历时,如果channel没有关闭,则会出现deadlock的错误。因为如果不关闭管道,管道就没有结束标志,程序会等待管道继续填入数据,从而导致死锁。
func main() {
intChan := make(chan int, 100)
for i := 0; i < 66; i++ {
intChan <- i
}
// 遍历管道时不能使用普通的for循环
// 对管道的遍历必须使用for range
for v := range intChan {
fmt.Println("v: ", v)
}
}
2. 在遍历时,如果channel已经关闭,则可以正常遍历数据。
func main() {
intChan := make(chan int, 100)
for i := 0; i < 66; i++ {
intChan <- i
}
close(intChan)
for v := range intChan {
fmt.Println("v: ", v)
}
}
goroutine和channel搭配使用
// write Data
func writeData(intChan chan int) {
for i := 0; i < 6; i++ {
//放入数据
intChan <- i
fmt.Println("writeData写了数据: ", i)
time.Sleep(time.Second)
}
close(intChan)
}
// read Data
func readData(intChan chan int, exitChan chan bool) {
for {
v, ok := <-intChan
if !ok {
break
}
fmt.Println("readData读到数据: ", v)
time.Sleep(time.Second)
}
// 读取完数据后,任务完成
exitChan <- true
close(exitChan)
}
func main() {
//创建两个管道
intChan := make(chan int, 50)
exitChan := make(chan bool, 1)
go writeData(intChan)
go readData(intChan, exitChan)
for {
_, ok := <-exitChan
time.Sleep(time.Second)
fmt.Println("OK的值为: ", ok)
if !ok {
break
}
}
}
channel的阻塞
当向管道内写入的数据量超过了管道的容量时就会发生阻塞而报deadlock错误
func main() {
intChan := make(chan int, 10)
for i := 0; i < 50; i++ {
//放入数据
intChan <- i
fmt.Println("writeData写了数据: ", i)
}
}
底层的编译器如果发现还有其他协程在读数据时,则不会报死锁错误,但如果没有其他协程在读,而单纯是超过了管道的容量,则会造成死锁。即管道内的数据并非处于流动状态
// write Data
func writeData(intChan chan int) {
for i := 0; i < 10; i++ {
//放入数据
intChan <- i
fmt.Println("writeData写了数据: ", i)
time.Sleep(time.Second)
}
close(intChan)
}
// read Data
func readData(intChan chan int, exitChan chan bool) {
for {
v, ok := <-intChan
if !ok {
break
}
fmt.Println("readData读到数据: ", v)
time.Sleep(time.Second)
}
// 读取完数据后,任务完成
exitChan <- true
close(exitChan)
}
func main() {
//创建两个管道
intChan := make(chan int, 5)
exitChan := make(chan bool, 1)
go writeData(intChan)
go readData(intChan, exitChan)
for {
_, ok := <-exitChan
time.Sleep(time.Second)
fmt.Println("OK的值为: ", ok)
if !ok {
break
}
}
}
可以看到,即使管道容量只开了5,但是只要管道处于流动状态,编译器会让他自动阻塞,让管道的数据最终可以写完和读完。