Go---channel(管道)

需求,现在要计算1-200 的各个数的阶乘,并且把各个数的阶乘放入到map中。最后显示出来。
要求使用goroutine完成
➢分析思路:
1) 使用goroutine来完成,效率高,但是会出现并发/并行安全问题.
 2) 这里就提出了不同goroutine如何通信的问题
➢代码实现
1) 使用goroutine来完成(看看使用gorotine 并发完成会出现什么问题?然后我们会去解决)
2) 在运行某个程序时,如何知道是否存在资源竞争问题。方法很简单, 在编译该程序时,增加一
个参数-race 即可

go build -race main.go 


3)示意图: 

 

不同goroutine之间如何通讯
  1.全局变量加锁同步
  2. channel
1.使用全局变量加锁同步改进程序
   1)因为没有对全局变量m加锁,因此会出现资源争夺问题,代码会出现错误,提示
    concurrent map writes
  2)  解决方案: 加入互斥锁
  3) 我们的数的阶乘很大, 结果会越界

 在实际运行中,还是可能在红框部分出现( 运行时增加-race参数,确实会发现有资源竞争问题),主线程并不知道,协程已经执行完成,因此底层可能仍然出现资源争夺,因此加入互斥锁即可解决问题。

4)代码

package main

import (

    "fmt"

    "sync"

)

//1.编写一个函数,来计算各个数的阶乘,并放入到map中.

//2.我们启动的协程多个,统计的将结果放入到map中

//3.map应该做出一个全局的.

var (

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

    //声明一个全局的互斥锁

    //lock 是一个全局的互斥锁

    //sync 是包 : synchornized 同步

    //Mutex : 互斥

    lock sync.Mutex

)

func test(n int){

   //求阶乘

   res := 1

   for i := 1;i<=n;i++{

       res *= i

   }

   //放入到myMap

   //加锁

   lock.Lock()

   myMap[n]=res //  fatal error: concurrent map writes

   //解锁

   lock.Unlock()

}

func main(){

    //开起了200个协程完成这个任务

    for i := 1;i<=20;i++{

        go test(i)

    }

    //输出结果

    //加锁

    lock.Lock()

    for i,v := range myMap{

        fmt.Printf("map[%d]=%d\n",i,v)

    }

   //解锁

    lock.Unlock()

}

channel

 为什么需要channel
前面使用全局变量加锁同步来解决goroutine的通讯,但不完美
1) 主线程在等待所有goroutine全部完成的时间很难确定,我们这里设置10秒,仅仅是估算。
2) 如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能还有goroutine处于工作状态,这时也会随主线程的退出而销毁
3) 通过全局变量加锁同步来实现通讯,也并不利用多个协程对全局变量的读写操作。
 

channel的介绍
1) channle本质就是一个数据结构-队列
2) 数据是先进先出
3) 线程安全,多goroutine访问时, 不需要加锁,就是说channel本身就是线程安全的


4) channel时 有类型的,一个sring的channel只能存放string类型数据。

 定义/声明channel
var 变量名  chan  数据类型
举例:
var  intChan  chan  int   (intChan用于存放int数据)
var  mapChan  chan map[int]string  (mapChan用于存放map[int]string类型)
var  perChan   chan  Person
var  perChan2  chan  *Person

注意:
1) channel是引用 类型
2) channel必须初始化才 能写入数据,即make后才能使用
3) 管道是有 类型的,intChan 只能写入整数int

简单使用

    //1.创建一个可以存放3个int类型的管道

    var intChan chan int

    intChan = make(chan int,3)

    //2.打印intChan

    fmt.Printf("intChan 的值=%v intChan本身的地址=%p\n",intChan,&intChan)

    //3.先管道写入数据

    intChan<- 10

    num := 985

    intChan<- num

    //注意 : 当我们给管道写入数据时,不能超过其容量 会报告deadlock

    //4.intChan的容量(cap)和长度

    fmt.Printf("intChan len=%v intChan cap=%v\n",len(intChan),cap(intChan)) //2,3

    //5.从管道中读取数据

    var num2 int

    num2 = <-intChan

    fmt.Println("num2=",num2)

    fmt.Printf("intChan len=%v intChan cap=%v\n",len(intChan),cap(intChan))

    //6.在没有使用协程的情况下,如果我们的管道数据已经全部取出,再取就会报告deadlock

 channel使用的注意事项
1)channel中只能存放指定的数据类型
2)channle的数据放满后,就不能再放入了
3)如果从channel取出数据后,可以继续放入
4)在没有使用协程的情况下,如果channel数据取完了,再取,就会报dead lock

channel练习

1) 创建一个mapChan,最多可以存放10个map[string]string的key-val , 演示写入和读取。

    var mapChan chan map[string]string

    mapChan = make(chan map[string]string, 10)

    m1 := make(map[string]string, 20)

    m1["city1"] = "北京"

    m1["city2"] = "天津"

    m2 := make(map[string]string, 20)

    m2["hero1"] ="宋江"

    m2["hero2"] ="武松"

    mapChan <- m1

    mapChan <- m2

    m3 := <-mapChan

    fmt.Printf("m1=%v \n",m3)

2)创建一个allChan,最多可以存放任意数据类型变量,演示写入和读取的用法

//定义一个存放任意数据类型的管道 3个数据

    //var allChan chan interface{}

    allChan := make(chan interface{},3)

    allChan<- 67

    allChan<- "mary"

    cat := Cat{"tom",6}

    allChan<- cat

    //我们希望获得管道中第三个元素,则先将前两个2取出

    <-allChan

    <-allChan

    newCat := <-allChan

    fmt.Printf("newCat=%T,newCat=%v \n",newCat,newCat)

    //下面写法错误

    //fmt.Printf("newCat.Name=%v",newCat.Name)

    //使用类型断言

    a := newCat.(Cat)

    fmt.Printf("a=%v",a.Name)

type Cat struct{

    Name string

    Age int

}

channel的关闭
使用内置函数close可以关闭channel,当channel关闭后, 就不能再向channe|写数据
了,但是仍然可以从该channel读取数据.

    var mapChan chan map[string]string

    mapChan = make(chan map[string]string, 10)

    m1 := make(map[string]string, 20)

    m1["city1"] = "北京"

    m1["city2"] = "天津"

    m2 := make(map[string]string, 20)

    m2["hero1"] ="宋江"

    m2["hero2"] ="武松"

    mapChan <- m1

    mapChan <- m2

    //关闭管道

    close(mapChan)

    //读数据

    m3 := <-mapChan

    fmt.Printf("m1=%v  len = %v\n",m3,len(mapChan)) //正常

    //测试存放数据 不能存放数据了

      //mapChan <- m1  //panic: send on closed channel

channel的遍历
channel支持for-range的方式进行遍历,请注意两个细节
1) 在遍历时,如果channel没有关闭,则会出现deadlock的错误
2) 在遍历时,如果channel已经关闭,则会正常遍历数据,追历完后,就会退出遍历。

//遍历管道

    intChan := make(chan int ,100)

    for i:=0;i<100;i++{

        intChan<- i*2 //放入100个数据放到管道中

    }

    //关闭管道

    close(intChan)

    //输出管道的数据

    for v := range intChan {

        fmt.Println("v=",v)

    }

练习

请完成goroutine和channel协同工作的案例, 具体要求:
1) 开启一个writeData协程, 向管道intChan中写入50个整数.
2)开启一个readData协程,从管道intChan中读取writeData写入的数据。
3) 注意: writeData和readDate操作的是同一个管道
4) 主线程需要等待writeData和readDate协程都完成工作才能退出[管道]

分析

代码

package main

import (

    "fmt"

)

//write data

func writeData(intChan chan int){

    for i := 1;i<=50;i++{

        //存放数据

        intChan<- i

        fmt.Println("writeData data=",i)

    }

    //关闭管道

    close(intChan)

}

//read data

func readData(intChan chan int,exitChan chan bool){

      //读数据

    for{

        v,ok := <-intChan

        if !ok {

            break

        }

        fmt.Printf("readData data=%v\n",v)

    }

   //读完数据后,即任务完成

   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

        if !ok {

            break

        }

    }

}

如果编译器(运行)发现一个管道只有写,而没有读,则改管道,会阻塞。
写管道和读管道的频率不一致,无所谓。

练习

需求:要求统计1-8000的数字中,哪些是素数?

思路分析

代码

package main

import (

   "fmt"

   "time"

)

//向 intChan 放入 1-8000个数

func putNum(intChan chan int){

    for i:=1;i<= 8000;i++{

        intChan<-i

    }

    //关闭intChan

    close(intChan)

}

func primeNum(intChan chan int,primeChan chan int,exitChan chan bool){

    //使用for 循环

    var flag bool

    for {

        num,ok := <-intChan

        if !ok { //intChan 取不到了

            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("有一个协程因为取不到数据,退出了")

    //还不能关闭primeChan管道

    exitChan<-true

}


 

func main(){

    //需求:要求统计1-200000的数字中,哪些是素数?

    intChan := make(chan int ,1000)

    primeChan := make(chan int,2000)

    //标识退出的管道

    exitChan := make(chan bool,4)

    //计算开始时间

    start := time.Now().Unix()

    //开启一个协程,向intChan放入1-8000函数

    go putNum(intChan)

   //开启4个协程,从intChan取出数据, 并判断是否为素数,如果是,就

   //放入到primeChan

   for i:=0; i<4 ;i++{

       go primeNum(intChan,primeChan,exitChan)

   }

   //主线程,进行处理

   go func(){

       //从exitChan取出所有值

       for i:=0;i<4;i++{

         <-exitChan

        }

        //结束时间

        end := time.Now().Unix()

        fmt.Println("使用协程耗时=", end - start)

        //当我们从exitchan 取出了4个结果,就可以放心的关闭primeNum

        close(primeChan)

   }()

   //遍历我们的primeNum , 把结果取出

   for{

     // res,ok := <-primeChan

      _,ok := <-primeChan

      if !ok{

          break

      }

      //将结果输出

      //fmt.Printf("素数=%d\n",res)

   }

   fmt.Println("main exit")

}

 channel使用细节和注意事项
1) channel可以声明为只读,或者只写性质


2) channel只 读和只写的最佳实践案例

 

 

3) 使用select可以解决从管道取数据的阻塞问题

package main

import (

    "fmt"

    "time"

)

func main() {

    //使用select可以解决从管道取数据的阻塞问题

//1.定义一个管道10个数据int

intChan := make(chan int, 10)

 for i:=0;i<10;i++{

  intChan<- i

 }

//2.定义一个管道5个数据string

 stringChan := make(chan string,5)

  for i :=0;i< 5; i++ {

   stringChan <- "hello" + fmt.Sprintf("%d",i)

   }

//传统的方法在遍历管道时,如果不关闭会阻塞而导致deadlock

//问题, 在实际开发中, 可能我们不好确定 什么关闭该管道.

//可以使用select方式可以解决

 for {

    select{

           //注意:这里,如果intChan-直没有关闭,不会直阻寨 而deadlock

           //,会自动到下一个case匹配

            case v := <-intChan:

              fmt.Printf("从intChan读取到的数据%d\n",v)

              time.Sleep(time.Second)

            case v := <-stringChan:

                fmt.Printf("从stringChan读取到的数据%s\n",v)

                time.Sleep(time.Second)

            default:

                fmt.Printf("game over\n")

                time.Sleep(time.Second)

                return

    }

 }

}

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

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

package main

import (

    "fmt"

    "time"

)

func sayHello(){

    for i := 0 ;i<10;i++{

        time.Sleep(time.Second)

        fmt.Println("hello,world")

    }

}

func test(){

    //捕获panic

    defer func(){

        //捕获test抛出的panic

        if err := recover();err != nil {

            fmt.Println("test() 发生错误",err)

        }

    }()

    //定义了一个map

    var myMap map[int]string

    myMap[0]="boys" //error

}

func main(){

    go sayHello()

    go test()

    for i := 0 ;i<10;i++{

        fmt.Println("main() ok=",i)

        time.Sleep(time.Second)

    }

}

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值