zoukankan      html  css  js  c++  java
  • Golang channel底层原理及 select 和range 操作channel用法

    Golang通过通信来实现共享内存,而不是通过共享内存而实现通信,通信实际就是借用channel来实现的

    channel底层数据结构

    type hchan struct {
        qcount   uint           
        dataqsiz uint           
        buf      unsafe.Pointer #有缓冲的channel所特有的结构,用来存储缓存数据。是个循环链表
        elemsize uint16
        closed   uint32
        elemtype *_type 
        sendx    uint          #buf这个循环链表中的发送或接收的index
        recvx    uint         
        recvq    waitq       #接收(<-channel)或者发送(channel <- xxx)的goroutine抽象出来的结构体(sudog)的队列。是个双向链表
        sendq    waitq
        lock mutex
    }

    channel 发送send(ch <- xxx) 和 接收recv(<- ch)的逻辑

     如果要让元素先进先出的顺序进入到buf循环链表中,就需要加锁,hchan结构中本身就携带了一个互斥锁mutex。

    当使用send (ch <- xx)或者recv ( <-ch)的时候,先要锁住hchan这个结构体。

     具体过程:

     加锁->传递数据->解锁

    在channel阻塞时,goroutine如何调度

    G1
    ch := make(chan int,1)
    ch <-1
    #阻塞
    ch <-1

    当阻塞的channel再次send数据时,会主动让出调度器,让G1等待,让出M,由其他Groutine去使用,此时G1会被抽象成含有G1指针和send元素的sudog结构体保存到hchan的sendq中等待被唤醒。

    当G2执行了recv操作p := <-ch时

    G2从缓存队列中取出数据,channel会将等待队列中的G1推出,将G1当时send的数据推到缓存中,然后调用Go的scheduler,唤醒G1,并把G1放到可运行的Goroutine队列中

    channel结合select简单使用

    所有的case都阻塞时,默认执行default中的值,多个case可以执行时候,随便选择一个case

    ch := make(chan int, 1)
    for i := 0; i < 10; i++ {
        select {
        case x := <-ch:
            fmt.Println(x) 
        case ch <- i:
        }
    }

    输出0,2,4,6,8

    某个请求时间过程,通过default释放资源

    select {
        case <-ch:
            // ch1所在的发送端goroutine正在处理请求
        case <-time.After(2*time.Second):
            // 释放资源,返回请求处理失败的数据,或者先通知用户已处理成功,最终一致性可以保证。
            // 最重要的是快速响应,免得用户看着页面没反应,过多的点击按钮发送请求,会过多消耗服务端的系统资源
    }

     Go提供了range关键字,将其使用在channel上时,会自动等待channel的动作一直到channel被关闭,多个goroutine可以借助range操作一个channel

    package main
    
    import (
        "fmt"
    )
    
    // 开启5个goroutine对channel中的每个值求立方
    func oprate(task chan int, exitch chan bool) {
        for t := range task { //  处理任务
            fmt.Println("ret:", t*t*t)
        }
        exitch <- true
    }
    func main() {
        task := make(chan int, 1000) //任务管道
        exitch := make(chan bool, 5) //退出管道
        go func() {
            for i := 0; i < 1000; i++ {
                fmt.Println("in:", i)
                task <- i
            }
            close(task)
        }()
        for i := 0; i < 5; i++ { //启动5个goroutine做任务
            go oprate(task, exitch)
        }
    
        for i := 0; i < 5; i++ {
            <-exitch
        }
        close(exitch) //关闭退出管道
    }

    5个goroutine并发对任务管道的1000个值进行处理,in是按顺序的,ret不一定按顺序

    只读或者只写channel并不是存在的,但只是作为一种形式存在着,例如可以在函数的参数里定义参数只读或者只写

    package main
    
    import (
        "fmt"
        "time"
    )
    
    //只能向chan里写数据
    func send(c chan<- int) {
        for i := 0; i < 10; i++ {
            c <- i
        }
    }
    //只能取channel中的数据
    func get(c <-chan int) {
        for i := range c {
            fmt.Println(i)
        }
    }
    func main() {
        c := make(chan int)
        go send(c)
        go get(c)
        time.Sleep(time.Second*1)
    }

    对channel读写频率的控制,借助time.Tick(time)实现

    复制代码
    package main
    
    import (
        "time"
        "fmt"
    )
    
    func main(){
        requests:= make(chan int ,5)
        for i:=1;i<5;i++{
            requests<-i
        }
        close(requests)
        limiter := time.Tick(time.Second*1)
        for req:=range requests{
            <-limiter
            fmt.Println("requets",req,time.Now()) //执行到这里,需要隔1秒才继续往下执行,time.Tick(timer)上面已定义
        }
    }
    //结果:
    requets 1 2018-07-06 10:17:35.98056403 +0800 CST m=+1.004248763
    requets 2 2018-07-06 10:17:36.978123472 +0800 CST m=+2.001798205
    requets 3 2018-07-06 10:17:37.980869517 +0800 CST m=+3.004544250
    requets 4 2018-07-06 10:17:38.976868836 +0800 CST m=+4.000533569

    如何正确关闭channel?

    channel 关闭了接着 send 数据会发生panic,关闭一个已经关闭的 channel 会发生panic

    The Channel Closing Principle:在使用Go channel的时候,一个适用的原则是不要从接收端关闭channel,也不要关闭有多个并发发送者的channel。换句话说,如果sender(发送者)只是唯一的sender或者是channel最后一个活跃的sender,那么你应该在sender的goroutine关闭channel,从而通知receiver(s)(接收者们)已经没有值可以读了。维持这条原则将保证永远不会发生向一个已经关闭的channel发送值或者关闭一个已经关闭的channel。

  • 相关阅读:
    域名系统
    DNS域名解析过程
    服务器常用的状态码
    重绘与重排及它的性能优化
    console.time和console.timeEnd用法
    用CSS开启硬件加速来提高网站性能
    公钥和私钥
    svn conflict 冲突解决
    svn分支开发与主干合并(branch & merge)
    源生js惯性滚动与回弹效果
  • 原文地址:https://www.cnblogs.com/peterleee/p/13428390.html
Copyright © 2011-2022 走看看