四十三golang--管道

假设我们现在有这么一个需求:

计算1-200之间各个数的阶乘,并将每个结果保存在map中,最终显示出来,要求使用goroutine。

分析:

(1)使用goroutine完成,效率高,但是会出现并发/并行安全问题;

(2)不同协程之间如何通信;

  • 对于(1):不同协程之间可能同时对一块内存进行操作,导致数据的混乱,即并发/并行不安全;主协程运行完了,计算阶乘的协程却没有运行完,功能并不能够准确实现;可利用互斥锁解决该问题;
  • 对于(2):可以利用利用管道;

正常的代码:

package main

import (
    "fmt"
    sync"
)

var (
    myMap = make(map[int]int,10)
)

func cal(n int) {
    res := 1
    for i := 1; i <= n; i++ {
        res *= i
    }
    myMap[n] = res
}

func main() {
    1; i <= 15; i++ {
        go cal(i)
    }
    for i,v := range myMap {
        fmt.Printf(map[%d]=%d\n,i,v)
    }
}

运行结果:会报错

1.利用互斥锁 

"
  ""  
) //lock是全局互斥锁,synchornized lock sync.Mutex ) func cal(n i } lock.Lock() myMap[n] = res .Unlock() } func main() { { go cal(i) } range myMap { fmt.Printf(

有可能主程序运行完了而cal还没运行完(上面结果只到13,没有14,15),需要加上time.Sleep(time.Seconde*3),而在输出时,由于主协程并不知道程序已经完成了,底层仍然可能出现竞争资源,所以在输出阶段也要加上互斥锁。最终代码如下:

lock sync.Mutex
)

func cal(n  i
    }
    .Lock()
    myMap[n] = res
    .Unlock()
}

func main() {
     {
        go cal(i)
    }

    time.Sleep(time.Second * 4)

    lock.Lock()
    .Unlock()
}

为什么需要管道?

(1)主线程在等待所有协程全部完成的时间很难确定;

(2)如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能协程还处于工作状态,这时也会随着主协程的结束而销毁;

(3)通过全局变量加锁同步来实现通讯,也并不利于多个协程对全局变量的读写操作;

管道的介绍:
(1)管道的本质就是一种数据结构--队列;

(2)数据先进先出;

(3)线程安全,多协程访问时,不需要加锁;

(4)管道只能存储相同的数据类型;

管道的声明:

var intChan chan int;

var stringChan chan string;

var mapChan chan map[int]string;

var perChan chan Person;

var perChan chan *Person;

注意:管道是引用类型;管道必须初始化后才能写入数据;管道是有类型的,即IntChan只能写入int;

管道初始化:

var intChan chan int

intChan = make(chan int,10) 

向管道中读写数据:

num := 10

intChan<-num

var num2 int

num2<-intChan

注意:管道容量满了则不能继续写入,在没有使用协程的情况下,管道空了不能继续读取。

如何使管道中存储任意数据类型?

channel的关闭:

使用内置的close可以关闭管道,关闭后不能再进行写入,但是可以进行读取;

channel的遍历:

channel可以使用for range进行遍历 ,但是要注意:

  • 在遍历时,如果channel没有关闭,则会出现deadlock错误;
  • 在遍历时,如果channel已经关闭,则会正常遍历数据,遍历完成后退出;(即在遍历前需要先关闭管道)

2.利用管道实现边写边读

流程图:

int) map[ {
    res := return myMap
}

func write(myChan chan map[) {
    0; i <=  {
        myChan <- cal(i)
        fmt.Println(writer data:boolfor {
        v,ok := <-myChan
        if !ok {
            break
        }
        fmt.Println(read data:true
    close(exitChan)
}

func main() {
    var myChan chan map[
    myChan = make(chan map[20var exitChan chan 
    exitChan = make(chan bool,1)">1)
    go write(myChan)
    go read(myChan,exitChan)
     {
        _,1)">exitChan
        
        }
    }

}

结果:

思考:假设我们注销掉go read(myChan,exitChan)会发生什么呢?

也就是说,只有写入myChan而没有读取myChan,当存入myChan里面的数据达到了myChan的容量,再继续存入就会报deadlock错误。同时,由于exitChan需要写入一个true,而exitChan需要读取完myChan中的数据后才写入一个true,但是现在不能进行读取,也就是说,true不会写入exitChan,就形成了阻塞。假设我们打开go read(myChan,exitChan),我们设置其每隔1秒才读取一条数据,而写入则让其正常运行,也就是说,写入很快,读取很慢,这样会导致deadlock吗?答案是不会,只要有读取,golang会有个机制,不会让myChan存储的值超过myChan的容量。

管道的使用注意事项:

(1)在默认情况下,管道是双向的。管道是可以声明是只读还是只写;

  var intChan chan<-int(只写)

  intChan = make(chan int,3)

    var intChan2 <-chan int

(2)使用select可以解决从管道取数据阻塞问题;

func Test2() {

    intChan := make(chan 0; i < 10; i++ {
        intChan <- i
    }
    strChan := make(chan string,1)">55; i++ {
        strChan <- hello" + fmt.Sprintf(%d传统方法是可用close关闭,但是当不知道什么时候需要关闭时,这就不可用
    实际开发中可以使用select解决
     {
        selectcase v := <-intChan:
            fmt.Printf(从intChan中读取数据%d\nstrChan:
            fmt.Printf(从strChan中读取数据%s\ndefault:
            fmt.Println(都取不到数据了)
            
        }
    }

}

运行结果:

 (4)goroutine中使用recover,解决协程中出现panic,导致程序崩溃问题。

说明:如果我们建立了一个协程,但是这个协程出现了panic,如果我们没有捕获这个panic,则会造成整个程序的崩溃,这时,我们可以在goroutine中使用recover来捕获panic,进行处理,这样即使这个协程发生了问题,但是主线程仍然不受影响。

time
)

func sayHello() {
     {
        time.Sleep(time.Millisecond * 3)
        fmt.Println()
    }

}

func test() {
    这里我们可以使用defer revover解决nil
    defer func() {
        if err := recover(); err != nil {
            fmt.Println(test()发生错误,error=var myMap map[string
    myMap[0] = golang
}
func main() {
    go sayHello()
    go test()
    main() ok=

相关文章

类型转换 1、int转string 2、string转int 3、string转float ...
package main import s &quot;strings&quot; import...
类使用:实现一个people中有一个sayhi的方法调用功能,代码如...
html代码: beego代码:
1、读取文件信息: 2、读取文件夹下的所有文件: 3、写入文件...
配置环境:Windows7+推荐IDE:LiteIDEGO下载地址:http:...