go channel | 青训营

95 阅读1分钟

协程问题

  • 通过全局变量加锁同步来实现通讯,并不利于多个协程对全局变量的读写操作。
  • 加锁虽然可以解决goroutine对全局变量的抢占资源问题,但是影响性能,违背了原则。
  • 解决:使用channel进行协程goroutine间的通信。

channel

  • Go语言的并发模型是CSP(Communicating Sequential Processes)
    • 提倡通过通信共享内存而不是通过共享内存而实现通信,因此引出了channel。
    • 线程与协程的关系
      • 一个操作系统线程对应用户态多个goroutine
      • go程序可以同时使用多个操作系统线程
      • goroutine和OS线程是多对多的关系,即m:n。
  1. for range 走ch1,ch2
    • for range 从通道中取值,通道关闭时for range 退出
    • ch1:=make(chan int)
      ch2:=make(chan int)
      //goroutine开启
      go func(){
          for i:=0;i<100;i++{
              ch1<-i
          }
          close(ch1)
      }()
      //goroutine开启,从ch1取值
      go func(){
          for{
              i,ok:=<-ch1//通道取值后,平方走
              if ok{
                  ch2<-i*i
              }else{
                  break
              }
              ch1<-i*i
          }
          close(ch2)
      }()
      
      for i:=range ch2{
         fmt.Println(i)
      }
      
  2. 单通道,只读和只写
    • defer 用于通道关闭
    • in chan<- int:写
    • out <-chan int: 读
    • func counter(in chan<- int) { 
          defer close(in) 
          for i := 0; i < 100; i++ { 
              in <- i 
              } 
          } 
      
      func square(in chan<- int, out <-chan int) { 
          defer close(in) 
          for i := range out { 
              in <- i * i 
          } 
      }
      func output(out <-chan int) { 
          for i:=range out{
              fmt.Println(i) 
          } 
      }
      
      func main() { 
          ch1 := make(chan int) 
          ch2 := make(chan int) 
          go counter(ch1) 
          go square(ch2, ch1) 
          output(ch2) 
      }
      
  3. work pool 防止goroutine暴涨和泄漏

    •   func worker(id int, jobs <-chan int, results chan<- int) { 
            for j := range jobs { 
                //开始处理事务id与任务job
                fmt.Printf("worker:%d start job:%d\n", id, j) 
                //设定合适的时间
                time.Sleep(time.Second) 
                //结束处理事务id与任务job
                fmt.Printf("worker:%d end job:%d\n", id, j) 
                //传入chan:results中
                results <- j * 2 
            } 
        } 
        func main() { 
            jobs := make(chan int, 100) 
            results := make(chan int, 100) 
      
            // 先开启3个goroutine(w=1,2,3)
            for w := 1; w <= 3; w++ { 
                go worker(w, jobs, results) 
            } 
            // 为chan jobs传入j的5个任务(1,2,3,4,5) 
            for j := 1; j <= 5; j++ { 
                //传入后,worker可以进行处理,results通道接收到值
                jobs <- j 
            } 
            //传完结束
            close(jobs) 
            
            //继续传
            for a := 1; a <= 5; a++ { 
                 <-results 
            } 
         }
      
  4. select、case
    • select:(使用select语句能提高代码的可读性)
    • case:(如果多个case同时满足,select会随机选择一个。)
    • 对于没有case的select{}会一直等待,可用于阻塞main函数。
    •   //可处理一个或多个channel的发送/接收操作。 
        // ch := make(chan int, 1) 
        go func() { 
            for i := 0; i < 10; i++ { 
                select { 
                    case x := <-ch: 
                        fmt.Println(x) 
                    case ch <- i: 
                } 
            } 
        }()
      

查漏补缺

  • 打印变量类型

    • fmt.Printf 函数格式中的%T 动词(优先,效率高)
    • 使用反射包reflect 中的TypeOf 函数
  • switch更好用

    • case后支持多个参数 case 1,2,3,4:
    • case后加判断 case a>10:
  • fallthrough

    • switch:每个 case 无需声明 break 来终止
    • 想顺序执行使用 fallthrough
      • case 10:下面的语句最后加fallthrough,就可以像c++switch一样往下继续遍历
  • array和slice

    • array

      • 固定长度,元素类型两个字段
      • var a = [5]int{1, 2, 3, 4, 5}
    • slice

      • 不固定长度,字段:有指向一个数组的指针,len和cap共三个
      • var nums = []int{1, 2, 3, 4, 5, 6}
    • 初始化

      • 直接声明 slice 的方式内部是不申请内存空间的,slice 内部 array 指针指向 null。
      • 使用 make 关键字会申请包含 0 个元素的内存空间,底层 array 指针指向申请的内存。
      • image.png
      • 序列化
        • json.Marshal(直接声明): 返回 null
        • json.Marshal(make关键字初始化): 返回 []

总结

  • 很多要学的东西,继续加油咯