• Golang中Channel使用的一点随想


    前言(为什么要写这篇文章)

    在Golang中,搞同步/并发控制的方法有很多,有channel(管道),WaitGroup(等待线程结束),context(上下文管理),我一直想深入研究一下它们,因为这次开发我遇到了很多比较棘手的问题,我认为万变不离其宗,所以我看了一下他们的源码,然后简单的写了几个Demo,结合了我自己的开发经验,写成此文,做记录的同时,希望可以帮到其他兄弟,未来我还会出context随想,waitgroup随想,一点一点来吧。

    什么是channel

    首先你要了解两个东西,一个是goroutine,一个是CSP(Communicating Sequential Processes)

    goroutine:Go协程,比线程小,内存占用小,Go的主打

    CSP:一种模型,并发模型,说白了,就是依赖channel,认为信息的载体更加重要。这里相对来说比较复杂,请大家参考 http://www.usingcsp.com/cspbook.pdf 去学习一个。

    那么channel到底是什么呢,其实就是一种在goroutine之间通用的通信方式,这么理解吧,军队每天都看到人守夜,每天都有不同的口号,比如哨兵a,哨兵b进行交接的时候,就要对暗号,我们理解一下,哨兵a,b是两个goroutine,那么口令就是channel,通过channel,我们可以控制哨兵下岗,上岗,巡逻,那么换算到goroutine中,我们就可以控制goroutine启动,停止,这就是channel的牛逼之处。

    channel长什么样?(我们怎么定义channel)

    channel的定义方法非常简单,利用chan 关键字就可以

    test := make(chan bool)
    

    这里的make,没有定义缓存值,channel可以定义一个缓存值,例如

    make(chan int, 100)
    

    这里代表channel 的缓存大小为100,如果不设置缓存值,那么channel没有缓存,在没有通讯的情况下,将被阻塞。

    是的,这个就定义好了,现在你就有一个名叫test的bool类型的channel,你可能会问,卧槽,这东西有什么用?行,我马上就举个例子告诉你这玩意儿怎么使。我知道你们大部分都不爱看理论,只爱看解决问题的模型,没问题,我惯你!

    来个channel的并发例子

    你细分析分析,一般你什么时候会用到goroutine间通信?那不就是一个不够使,多开几个,增加咱们program的效率嘛。是,你可以用 go example() 这种类型,开它十几二十个,但是你怎么知道他们结束了呢?估摸着你写sleep啊,就像

    listip := []string{"10.0.9.11","10.0.9.22","10.0.9.33"}
    for _, ip := range(listip) {
        //假设我们执行一个ping ip 的逻辑
        go PingIPWork(ip)
    }
    time.sleep(time.Second * 5)
    

    这样其实没啥毛病,因为你不加那个sleep,人家估摸着就会一闪而过,你什么消息都收不到,我们这里有三个Ip,相当于你 go PingIPWork(ip)这里执行,外部的主程会直接退出去,人家才不管你搞完没呢,你又没和人家说是吧,所以channel就是干这个的,就是告诉主程,里面还有人呢嘿,别关门!

    listip := []string{"10.0.9.11","10.0.9.22","10.0.9.33"}
    ch := make(chan struct{})
    for _, ip := range listip {
        go func(ip string) {
            //假设执行一个ping ip的逻辑
            go PingIPWork(ip)
            ch <- struct{}{}
        }(ip)
    }
    for range ips {
        <-ch
    }
    

    这里去并发去执行PingIPWork,并且主程会等待所有goroutine完成之后才会彻底退出。发现了没,channel和goroutine一般都是放在一起的。

    在for...i range中使用channel

    有时候我们需要阻塞range,或者是控制循环不退出,这时候就可以用到channel

    listip := ["10.0.9.11","10.0.9.22","10.0.9.33"]
    
    //创建channel
    forever := make(chan bool)
    
    go func() {
      for _, d := range msgs {
        log.Printf("Received a message: %s", d)
      }
    }()
    
    //阻塞,让它不退出
    <-forever
    

    在select中使用channel

    类似switch的骚操作,一般来说,你给人家整死循环了,你不得负责给人家搞退出,或者说满足必要条件退出

    // 创建 quit channel
    quit := make(chan string)
    // 启动生产者 goroutine
    c := boring("Joe", quit)
    // 从生产者 channel 读取结果
    for i := rand.Intn(10); i >= 0; i-- { fmt.Println(<-c) }
    // 通过 quit channel 通知生产者停止生产
    quit <- "Bye!"
    fmt.Printf("Joe says: %q
    ", <-quit)
    
    select {
    case c <- fmt.Sprintf("%s: %d", msg, i):
        fmt.Println("work....")
    case <-quit:
        quit <- "See you!"
        return
    }
    

    有些时候我们不想等那么久,所以我们需要(timeout)机制

    c1 := make(chan string, 1)
    go func() {
        time.Sleep(time.Second * 2)
        c1 <- "result 1"
    }()
    select {
    case res := <-c1:
        fmt.Println(res)
    case <-time.After(time.Second * 1):
        fmt.Println("timeout 1")
    }
    

    关闭一个channel

    不用的东西要打包放好,否则你开一大堆channel在那里,搞一大堆panic: send on closed channel很吼么? 随意关闭channel的姿势你也要学到,其实关闭的逻辑也很简单,关键字close

    c := make(chan int, 10)
    c <- 1
    close(c)
    

    这就关掉了,但是这样关不严谨,你还是能拿到c已经发出去的数据,而且还能不断的读到0值,所以一定要换个方法

    c := make(chan int, 10)
    close(c)
    i, ok := <-c
    fmt.Printf("%d, %t", i, ok) 
    

    这样就能判断,当close时,读到的值是零值还是正常值,也就避免了上面出现的那种情况,一直读,一直有。

    后记

    我感觉我学的还是不够深,我希望我的技术能更进一步,所以我还是要不断学习,我可以的,我会做到的。这里面其实都是些皮毛。。我感觉,我希望未来一定要多学多读多看。哈哈,这篇文章写完了我也要去吃饭了,今天我吃仔肺粉 + 锅盔,我去吃饭啦!有问题给我留言或者邮件吧!

    写于2020-05-25 19:36分

  • 相关阅读:
    SQL 高级语法 (一)
    SQL 基础语法
    Memcache数据备份和还原
    OSPF原理及配置
    kubernetes全栈监控部署
    非容器化Jenkins连接Kubernetes
    Jenkins+Ansible安装部署
    Gitlab+Nexus Maven部署
    部署docker私有仓库Harbor
    LVS实现Kubernetes集群高可用
  • 原文地址:https://www.cnblogs.com/Yemilice/p/12960489.html
Copyright © 2020-2023  润新知