管道(channel)
全局变量加锁
解决程序同步问题
package mainimport ("fmt""sync""time"
)//需求:现在要计算1-200的各个数的阶乘,并且把各个数的阶乘放入到map中。
//最后显示出来。要求使用goroutine完成
//思路:
//1,编写一个函数,来计算各个数的阶乘,并放入到map中.
//2.我们启动的协程多个,统计的将结果放入到 map 中
//3. map 应该做出一个全局的var (myMap = make(map[int]int, 10)//声明全局互斥锁//lock 是一个全局的互斥锁//sync 是包:synchronized//Mutex 是互斥lock sync.Mutex
)func test(n int) {
res := 1for i := 1; i <= n; i++ {
res *= i}//加锁lock.Lock()myMap[n] = res//解锁lock.Unlock()
}func main() {
//开启多个协程完成任务for i := 1; i <= 20; i++ {
go test(i)}//休眠time.Sleep(time.Second * 10)lock.Lock()//遍历结果for i, v := range myMap {
fmt.Printf("map[%d] = %d \n", i, v)}lock.Unlock()
}
前面使用全局变量加锁同步来解决goroutine的通讯,但不完美
- 主线程在等待所有goroutine全部完成的时间很难确定,我们这里设置10秒,仅仅是估算。
- 如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能还有goroutine处于工作状态,这时也会随主线程的退出而销毁
- 通过全局变量加锁同步来实现通讯,也并不利用多个协程对全局变量的读写操作。
- 上面种种分析都在呼唤一个新的通讯机制-channel
channel介绍
- channle本质就是一个数据结构-队列
- 数据是先进先出【FIFO : first in first out】
- 线程安全,多goroutine访问时,不需要加锁,就是说channel本身就是线程安全的
- channel时有类型的,一个string的channel只能存放string类型数据。
package mainimport "fmt"func main() {
//1.创建一个可以存放3个int类型的管道var intChan chan intintChan = make(chan int, 3)//intChan 是什么?fmt.Printf("intChan的值=%v intChan本身的地址=%v\n", intChan, &intChan)//3.向管道写入数据intChan <- 10num := 200intChan <- numintChan <- 100//注意://写入数据时,不能超过容量//4.看看管道的长度和容量fmt.Printf("chanel len = %v cap = %v\n", len(intChan), cap(intChan))//5.从管道中去读数据var num2 intnum2 = <-intChanfmt.Println("num2=", num2)fmt.Printf("chanel len = %v cap = %v\n", len(intChan), cap(intChan))//6.在没有使用协程的情况下,如果我们的管道数据已经全部取出,再取就会报告 deadlock}
注意事项
- channel中只能存放指定的数据类型
- channle的数据放满后,就不能再放入了
- 如果从channel取出数据后,可以继续放入
- 在没有使用协程的情况下,如果channel数据取完了,再取,就会报dead lock
package mainimport "fmt"type Cat struct {
Name stringAge int
}func main() {
//定义一个存放任意数据类型的管道allChan := make(chan interface{
}, 3)allChan <- 10allChan <- "tom"cat := Cat{
"描", 10}allChan <- cat//我们希望获得管道中的第三个元素,则将前两个推出<-allChan<-allChannewCat := <-allChan //从管道中取出的Cat是什么?fmt.Printf("newCat=%T, newCat= %v \n", newCat, newCat)a := newCat.(Cat) //类型断言fmt.Printf("newCat.Name=%v\n", a.Name)
}
channel的遍历和关闭
管道的关闭
使用内置函数close可以关闭channel,当channel关闭后,就不能再向channel写数据了,但是仍然可以从该channel读取数据。
package mainimport "fmt"func main() {
intChan := make(chan int, 3)intChan <- 300intChan <- 200close(intChan)//这时不能写入数据到chanel//管道关闭,读取数据是可以的n1 := <-intChanfmt.Println(n1)
}
管道的遍历
channel支持for-rarge的方式进行遍历,请注意两个细节
1)在遍历时,如果channel没有关闭,则回出现deadlock的错误
2)在遍历时,如果channel已经关闭,则会正常遍历数据,遍历完后,就会退出遍历。
//遍历管道
intChan2 := make(chan int, 100)
for i := 0; i < 100; i++ {
intChan2 <- i * 2 //放入一百个数据
}//在遍历时,如果channel没有关闭,则会出现deadlock的错误
//关闭管道
close(intChan2)//遍历管道不能使用for 循环
for v := range intChan2 {
fmt.Println("v=", v)
}
goroutine和channel结合案例1
package mainimport ("fmt"
)//write data
func writeData(intChan chan int) {
for i := 1; i <= 50; i++ {
//放入数据intChan <- ifmt.Println("writeDate ", i)}close(intChan)
}//read data
func readData(intChan chan int, exitChan chan bool) {
for {
v, ok := <-intChanif !ok {
break}fmt.Printf("readData 读到的数据 = %v\n", v)}//readData 读取完数据后,完成任务exitChan <- trueclose(exitChan)
}func main() {
//创建两个管道intChan := make(chan int, 50)exitChan := make(chan bool, 1)go writeData(intChan)go readData(intChan, exitChan)for {
_, ok := <-exitChanif !ok {
break}}
}
管道阻塞
如果只是向管道写入数据,而没有读取,就会出现阻塞而dead lock
goroutine和channel结合案例2
package mainimport "fmt"//想 intChan 放入 1-8000个数
func putNum(intChan chan int) {
for i := 0; i < 8000; i++ {
intChan <- i + 1}//关闭close(intChan)
}func primeNum(intChan chan int, primeChan chan int, exitChan chan bool) {
//使用for循环//var num intvar flag bool //标识是否为素数for {
num, ok := <-intChanif !ok {
break}flag = true//判断num是不是素数for i := 2; i < num; i++ {
if num%i == 0 {
flag = falsebreak}}if flag {
//将这个素数放入primeChanprimeChan <- num}}fmt.Println("有一个primeNum 协程 因为取不到数据 ,退出")//向退出的管道写入trueexitChan <- true
}func main() {
intChan := make(chan int, 1000)primeChan := make(chan int, 2000) //放入结果//标识退出的管道exitChan := make(chan bool, 4)//开启一个协程,想intChan放入 1-8000个数go putNum(intChan)//开启4个协程,从 intChan 取出数据,并判读是否为素数,//如果是就放入 primeChanfor i := 0; i < 4; i++ {
go primeNum(intChan, primeChan, exitChan)}go func() {
//这里主线程进行处理for i := 0; i < 4; i++ {
<-exitChan}//关闭管道close(primeChan)}()//遍历 primeNumfor {
res, ok := <-primeChanif !ok {
break}fmt.Println("素数=", res)}fmt.Println("main退出")
}
channel细节
channel只读、只写
package mainimport "fmt"func main() {
//管道可以声明为只读或者只写//1. 在默认情况下,管道都是双向//var chan1 chan int //双向,可读可写//2. 声明为只写var chan2 chan<- intchan2 = make(chan int, 3)chan2 <- 20fmt.Println("chan2 = ", chan2)//3.声明为只读var chan3 <-chan intnum2 := <-chan3fmt.Println("num2", num2)
}
使用select可以解决从管道取数据的阻塞问题
package mainimport "fmt"func main() {
//使用select可以解决从管道取数据的阻塞问题//1.定义一个管道 10 个数据intintChan := make(chan int, 10)for i := 0; i < 10; i++ {
intChan <- i}//2.定义一个管道 5 个数据 stingstringChan := make(chan string, 5)for i := 0; i < 5; i++ {
stringChan <- "hello" + fmt.Sprintf("%d", i)}//传统的方法在遍历管道时,如果不关闭会阻塞而导致deadlock//问题,在实际开发中,听能我们不好确定什么关闭该管道.//可以使用select方式可以解决//label:for {
select {
//注意:这里,如果intchan一直没有关闭,不会一直阻塞而deadlock-.//会自动到下一个case匹配case v := <-intChan:fmt.Printf("从intChan读取的数据%d\n", v)case v := <-stringChan:fmt.Printf("从stringChan读取的数据 %s \n", v)default:fmt.Println("都去不到了...")//break labelreturn}}
}
goroutine中使用recover,解决协程中出现panic,导致程序崩溃问题.
package mainimport ("fmt""time"
)func sayHello() {
for i := 0; i < 10; i++ {
time.Sleep(time.Second)fmt.Println("hello , world")}
}func test() {
//这里使用defer + recoverdefer func() {
//捕获test的panicif err := recover(); err != nil {
fmt.Println("test() 发生错误 ", err)}}()var myMap map[int]stringmyMap[0] = "golang" //error
}func main() {
go sayHello()go test()for i := 0; i < 10; i++ {
fmt.Println("main ok =", i)}
}
本人博客地址