Golang中的channel
- 为什么要使用channel
- channel的介绍
- channel的基本使用
- 定义/声明channel
- 管道的遍历和关闭
- channel的关闭
- channel的遍历
- goroutine和channel结合
- 应用实例1
- 应用实例2
- 案例
- 注意事项
为什么要使用channel
前面使用全局变量加锁同步来解决goroutine的通讯,但不完美
- 1.主线程在等待所有goroutine全部完成时间很难确定,我们这里设置10秒,仅仅是过段
- 2.如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能还有routine处于工作状态,这时也会随主线程的退出而销毁
- 3.通过全局白能量加锁同步来实现通讯,也并不利用多个协程对全局变量的读写操作
- 4.上面种种分析都在互换一个新的通讯机制-----channel
channel的介绍
- 1.channel本质就是一个数据结构-队列
- 2.数据是先进先出
- 3.线程安全,多goroutine访问时,不需要加锁,就是说channel本身就是线程安全的
- 4.channel是有类型的,一个string的channel只能存放string类型数据
channel的基本使用
定义/声明channel
var 变量名chan数据类型
举例:
var intChan chan int (intChan用于存放int数据)
var mapChan chan map[int]ssting (mapChan用于存放map[int]string类型)
var perChan chan Person
var perChan2 chan *Person
说明:
channel是引用类型
channel必须初始化才能写入数据,即make后才能使用
管道是有类型的,intChan只能写入整数
package main
import "fmt"
func main() {
//演示一下管道的使用
//创建一个可以存放三个int类型的俄管道
var intChan chan int
intChan = make(chan int, 3)
//看看intChan是什么
fmt.Printf("intChan的值=%v intChan本身的地址=%p\n", intChan, &intChan)
//向管道写入数据
intChan <- 10
num := 211
intChan <- num
//看看管道的长度和cap(容量)
fmt.Printf("channel len = %v cap=%v \n", len(intChan), cap(intChan))
//从管道中读取数据
var num2 int
num2 = <-intChan
fmt.Println("num2=", num2)
fmt.Printf("channel len =%v cap=%v \n", len(intChan), cap(intChan))
//在没有使用协程的情况下,如果我们管道数据已经全部取出,在取就会报告deadlock
}
/*
intChan的值=0xc00010e080 intChan本身的地址=0xc000006028
channel len = 2 cap=3
num2= 10
channel len =1 cap=3
*/
管道的遍历和关闭
channel的关闭
使用内置函数close可以关闭channel,当channel关闭后,就不能再想channel写数据了,但是仍然可以从给channel读取数据
channel的遍历
channel支持for-range的方式进行遍历
1.在遍历时,如果channel没有关闭,则会出现deadlock的错误
2.在遍历时,如果channel已经关闭,则会出现正常遍历数据,遍历完后,就会退出遍历
package main
import "fmt"
func main() {
intChan := make(chan int, 3)
intChan <- 100
intChan <- 200
close(intChan)
//这时不能够再写入到channel
//intChan <- 300
fmt.Println("okk")
n1 := <-intChan
fmt.Println("n1=", n1)
//遍历管道
intChan2 := make(chan int, 100)
for i := 0; i < 100; i++ {
intChan2 <- i * 2 //放入100个数据到管道
}
// 在遍历时,如果channel没有关闭,责护出现deadlock的错误
close(intChan2)
for v := range intChan2 {
fmt.Println("v=", v)
}
}
goroutine和channel结合
应用实例1
package main
import (
"fmt"
"time"
)
func writeData(intChan chan int) {
for i := 1; i < 50; 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
}
time.Sleep(time.Second)
fmt.Printf("readData读到数据=%v\n", v)
}
//readData读取完数据后,即任务完成
exitChan <- true
close(exitChan)
}
func main() {
//创建两个管道
intChan := make(chan int, 50)
exitChan := make(chan bool, 1)
go writeData(intChan)
go readData(intChan, exitChan)
//time.Sleep(time.Second * 10)
for {
_, ok := <-exitChan
if !ok {
break
}
}
}
应用实例2
如果只是向管道写入数据,而没有读取,就会出现阻塞而dead lock,原因是intChan容量是10,而带点吗writeData会写入50个数据,因此就会阻塞在writeData的ch<-i
案例
package main
import "fmt"
func putNum(intChan chan int) {
for i := 1; i <= 8000; i++ {
intChan <- i
}
//关闭intChan
close(intChan)
}
//开启四个协程,从intChan取出数据,并判断是否为素数
//如果是,就放入到primeChan
func primeNum(intChan chan int, primeChan chan int, exitChan chan bool) {
//使用for循环
var flag bool
for {
num, ok := <-intChan
if !ok {
break
}
flag = true //假定是素数
//判断num是不是素数
for i := 2; i < num; i++ {
if num%i == 0 { //说明该num不是素数
flag = false
break
}
}
if flag {
//将这个数就放入到primeChan
primeChan <- num
}
}
fmt.Println("有一个primeNum协程因为取不到数据,退出")
//这里还不能关闭primeChan
//向exitChan写入true
exitChan <- true
}
func main() {
intChan := make(chan int, 1000)
primeChan := make(chan int, 2000) //放入结果
//标识退出的管道
exitChan := make(chan bool, 4)
//开启一个协程,想in特产放入1-8000个数
go putNum(intChan)
//开启四个协程,从intChan取出数据,并判断是否为素数
//如果是,就放入到primeChan
for i := 0; i < 4; i++ {
go primeNum(intChan, primeChan, exitChan)
}
//这里进行主线程处理
go func() {
for i := 0; i < 4; i++ {
<-exitChan
}
//当我们从exitChan,去出了4个结果,就可以放心关闭primeChan
close(primeChan)
}()
//遍历primeNum,把结果输出
for {
res, ok := <-primeChan
if !ok {
break
}
//将结果输出
fmt.Printf("素数=%d\n", res)
}
fmt.Println("main线程退出")
}
注意事项
- 1.channel可以声明为只读,或者只写性质
- 2.channel只读和只写的最佳实践案例
package main
import "fmt"
func main() {
//管道可以声明为只读或者只写
//默认情况下,管道是双向的
//var chan1 chan int
//声明为只写
var chan2 chan<- int
chan2 = make(chan int, 3)
chan2 <- 20
//num := <-chan2
fmt.Println("chan2=", chan2)
//声明为只读
var chan3 <-chan int
num2 := <-chan3
fmt.Println("num2=", num2)
}
- 3.使用select可以解决从管道取数据的阻塞问题
- 4.goroutine中使用recover,解决携程中出现panic,导致程序奔溃问题
- 说明,如果我们起了一个协程,但是这个协程出现了panic,如果我们没有捕获这个panic,就会造成整个程序崩溃,这是我们可以在goroutine中使用recover来捕获panic,进行处理,这样及时这个协程发生的问题,但是主线程仍然不受影响,