当前位置: 代码迷 >> 综合 >> Golang管道(channel)及其应用
  详细解决方案

Golang管道(channel)及其应用

热度:39   发布时间:2023-11-26 21:35:51.0

管道(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的通讯,但不完美

  1. 主线程在等待所有goroutine全部完成的时间很难确定,我们这里设置10秒,仅仅是估算。
  2. 如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能还有goroutine处于工作状态,这时也会随主线程的退出而销毁
  3. 通过全局变量加锁同步来实现通讯,也并不利用多个协程对全局变量的读写操作。
  4. 上面种种分析都在呼唤一个新的通讯机制-channel

channel介绍

  1. channle本质就是一个数据结构-队列
  2. 数据是先进先出【FIFO : first in first out】
  3. 线程安全,多goroutine访问时,不需要加锁,就是说channel本身就是线程安全的
  4. channel时有类型的,一个string的channel只能存放string类型数据。
image-20220206211046515 image-20220206211235710
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}

注意事项

  1. channel中只能存放指定的数据类型
  2. channle的数据放满后,就不能再放入了
  3. 如果从channel取出数据后,可以继续放入
  4. 在没有使用协程的情况下,如果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

image-20220207110813584 image-20220207111517266
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

image-20220207114011116

goroutine和channel结合案例2

image-20220207114236556 image-20220207115058334
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)}
}

本人博客地址

  相关解决方案