21天速成go-第九天

68 阅读10分钟

13_channels_fan-out_fan-in

这一章节比较奇怪,写的都是死循环的输入管道和死循环的输出读取(想到这里,死循环的输出读取居然没报错 ? 应该是如果输出读取是在 goroutine 中,不阻塞主main的话,就不会报错)

做一个测试:

func Read(in chan int) {
   for {
      fmt.Printf("Read: %d\n", <-in)
   }
}

func Send(out chan int) {
   for {
      n := rand.Intn(10)
      out <- n
      fmt.Printf("Send: %d\n", n)
   }
}

func main() {
   in := make(chan int)
   go Send(in)
   go Send(in)
   go Send(in)
   
   go Read(in)
   go Read(in)
   go Read(in)
   
   time.Sleep(30 * time.Second)
}

反正一直就是正常运行,一直有东西发送。。。但是如果发送方,只发送10个呢,接收方死循环呢,卡主接收方,然后结束

改成这样:

func Read(in chan int) {
   for {
      fmt.Printf("Read: %d\n", <-in)
   }
}

func Send(out chan int) {
   defer close(out)
   for i := 0; i < 10; i++ {
      out <- i
      fmt.Printf("Send: %d\n", i)
   }
}

func main() {
   in := make(chan int)
   go Send(in)

   go Read(in)

   time.Sleep(30 * time.Second)
}

结果发现一个严重问题,一直打印的是Read 0 ,明明 <-in 读取到了不应该是下一个吗,为什么循环Read 0 卡主呢

func Read(in chan int) {
   //for {
   // fmt.Printf("Read: %d\n", <-in)
   //}
   fmt.Printf("Read: %d\n", <-in)
   fmt.Printf("Read: %d\n", <-in)
   fmt.Printf("Read: %d\n", <-in)
}

func Send(out chan int) {
   defer close(out)
   for i := 0; i < 10; i++ {
      out <- i
      fmt.Printf("Send: %d\n", i)
   }
}

func main() {
   in := make(chan int)
   go Send(in)

   go Read(in)

   time.Sleep(30 * time.Second)
}

改成这个样子就是

Read: 0
Send: 0
Send: 1
Read: 1
Read: 2
Send: 2

的卡主,因为只接受了2个,发送方没法继续发送了

这段代码存在严重问题

package main

import (
 "fmt"
 "time"
)

func Read(in chan int) {
 start_time := time.Now()
 defer func() {
  fmt.Printf("Read run time: %v\n", time.Now().Sub(start_time).Seconds())
 }()
 for {
  fmt.Printf("Read: %d\n", <-in)
 }
}

func Send(out chan int) {
 start_time := time.Now()
 defer func() {
  close(out)
  fmt.Printf("Send run time: %v\n", time.Now().Sub(start_time).Seconds())
 }()
 for i := 0; i < 10; i++ {
  out <- i
  fmt.Printf("Send: %d\n", i)
 }
}

func main() {
 start_time := time.Now()
 defer func() {
  fmt.Printf("Main run time: %v\n", time.Now().Sub(start_time).Seconds())
 }()

 in := make(chan int)
 go Send(in)
 go Read(in)

 time.Sleep(3 * time.Second)
}
  1. 应该只运行3秒钟,但是在 goland 里面会持续运行十几秒,一直刷新,在外秒命令行运行会变成4s
  2. 管道读取出来的值全部为0,全是0,且读取不完

对于第2个表现综合表述如下:

改成

for {
   fmt.Printf("Read: %d\n", <-in)
   time.Sleep(time.Second)
}

休眠1s的话,那么就是正常一发一收的打印,然后到了3秒结束很正常

如果缩短时间

for {
   fmt.Printf("Read: %d\n", <-in)
   time.Sleep(100 * time.Millisecond)
}

打印结果是

Read: 0
Send: 0
Read: 1
Send: 1
Read: 2
Send: 2
Read: 3
Send: 3
Read: 4
Send: 4
Send: 5
Read: 5
Read: 6
Send: 6
Read: 7
Send: 7
Read: 8
Send: 8
Read: 9
Send: 9
Send run time: 0.905213001
Read: 0
Read: 0
Read: 0
Read: 0
Read: 0
Read: 0
Read: 0
Read: 0
Read: 0
Read: 0
Read: 0
Main run time: 3.001045732

前期还很正常,但是后期打印出来的全部是0了,为什么后期会打印0呢 ?

对于一个关闭的管道:

  1. 发送方:任何尝试向已经关闭的管道发送数据的操作都将会导致 panic
  2. 接收方:可以从已经关闭的管道接收数据,直到管道中的数据被完全读取。一旦管道中所有的数据都被读取完毕,后续的接收操作 【将不会被阻塞】,而是立即返回一个零值,并且 ok 值为 false,表示管道已经关闭,不再有数据可以读取

完全忘记管道是会返回两个值的, val 和 ok ,之所以返回零值,是因为返回的你这个数据类型的0值,是int 就返回int

所以应该读取的时候判断一下 val, ok := <-out

int 管道关闭后,返回的都是0,然后是一个 ok=false, 那么如果是string 管道呢,也是返回0吗,都是用0代表错误吗,试试看

因为要是一个string,然后又要看到变化,所以打算把 变量 i 加入string里面去

package main

import (
   "fmt"
   "time"
)

func Read(in chan string) {
   start_time := time.Now()

   defer func() {
      fmt.Printf("Read run time: %v\n", time.Now().Sub(start_time).Seconds())
   }()

   for {
      val, ok := <-in
      fmt.Printf("Read val: [%v], ok: [%v]\n", val, ok)
      time.Sleep(100 * time.Millisecond)
   }

}

func Send(out chan string) {

   start_time := time.Now()

   defer func() {
      close(out)
      fmt.Printf("Send run time: %v\n", time.Now().Sub(start_time).Seconds())
   }()

   for i := 0; i < 10; i++ {
      msg := "hello, " + string(i)
      out <- msg
      fmt.Printf("Send: %v\n", msg)
   }
}

func main() {
   start_time := time.Now()
   defer func() {
      fmt.Printf("Main run time: %v\n", time.Now().Sub(start_time).Seconds())
   }()

   in := make(chan string)
   go Send(in)
   go Read(in)

   time.Sleep(3 * time.Second)
}

这个输出是

Read val: [hello, ], ok: [true]
Send: hello, 
Read val: [hello, ], ok: [true]
Send: hello, 
Read val: [hello,], ok: [true]
Send: hello, 
Read val: [hello,       ], ok: [true]
Send: hello,    
Send run time: 0.906575902
Read val: [], ok: [false]
Read val: [], ok: [false]
Read val: [], ok: [false]
Read val: [], ok: [false]
Main run time: 3.00046913

加入进去的i变量,压根就没有生成到 一开始是怀疑循环遍历 i 的问题,使用

for i := 0; i < 10; i++ {
   v := i
   //msg := "hello, " + string(i)
   msg := "hello, " + string(v)
   out <- msg
   fmt.Printf("Send: %v\n", msg)
}

也是打印出不来,做一个实验

fmt.Println("hello, " + string(v)

也打印不出来,说明加的有问题,于是考虑用 fmt.Sprintf

msg := fmt.Sprintf("hello, %d", i)

这样子是可以出来的,有效果的

但是如果

fmt.Println("hello, " + string(65)

就会出来字符了,出来的是 A,hello A 这就是说明 string转化的是字符集中的序号,因为我是特意用

>>> ord('A')
65

求出来A的码值然后再去添加打印的

那么如果要 int 转 string 怎么办呢,还要导入新的包,strconv

msg := "hello, " + strconv.Itoa(i)

这样子才可以

sync的atomic 用法

atomic 是 sync/atomic 中一个原子操作函数

var a int64
var wg sync.WaitGroup

func Add() {
   a++
   wg.Done()
}

func main() {
   n := 1000
   wg.Add(n)
   for i := 0; i < n; i++ {
      go Add()
   }
   wg.Wait()
   fmt.Printf("finally n = %d", a)
}

这个如果运行的是10的话,结果是正常的,如果是1000的话,结果是900多少加了,所以要用 atomic

func Add() {
   atomic.AddInt64(&a, 1)
   wg.Done()
}

注意一定是取地址的加,如果不定义全局变量的话,要传指针了

var wg sync.WaitGroup

func Add(ptr *int64) {
   atomic.AddInt64(ptr, 1)
   wg.Done()
}

func main() {
   n := 1000
   var a int64
   a = 0
   wg.Add(n)
   for i := 0; i < n; i++ {
      go Add(&a)
   }
   wg.Wait()
   fmt.Printf("finally n = %d", a)
}

注意都是取地址

atomic.AddInt64(&workerID, 1)
thisID := atomic.LoadInt64(&workerID)

挑战

来看这样一段代码,运行时候会报错,要把他调整好

package main

import (
    "fmt"
    "sync"
)

func main() {

    in := gen()

    // FAN OUT
    // Multiple functions reading from the same channel until that channel is closed
    // Distribute work across multiple functions (ten goroutines) that all read from in.
    xc := fanOut(in, 10)

    // FAN IN
    // multiplex multiple channels onto a single channel
    // merge the channels from c0 through c9 onto a single channel
    for n := range merge(xc...) {
       fmt.Println(n)
    }

}

func gen() <-chan int {
    out := make(chan int)
    go func() {
       for i := 0; i < 10; i++ {
          for j := 3; j < 13; j++ {
             out <- j
          }
       }
       close(out)
    }()
    return out
}

func fanOut(in <-chan int, n int) []<-chan int {
    xc := make([]<-chan int, n)
    for i := 0; i < n; i++ {
       xc = append(xc, factorial(in))
    }
    return xc
}

func factorial(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
       for n := range in {
          out <- fact(n)
       }
       close(out)
    }()
    return out
}

func fact(n int) int {
    total := 1
    for i := n; i > 0; i-- {
       total *= i
    }
    return total
}

func merge(cs ...<-chan int) <-chan int {
    var wg sync.WaitGroup
    out := make(chan int)

    output := func(c <-chan int) {
       for n := range c {
          out <- n
       }
       wg.Done()
    }

    wg.Add(len(cs))
    for _, c := range cs {
       go output(c)
    }

    // Start a goroutine to close out once all the output goroutines are
    // done.  This must start after the wg.Add call.
    go func() {
       wg.Wait()
       close(out)
    }()
    return out
}

/*
CHALLENGE #1:
-- This code throws an error: fatal error: all goroutines are asleep - deadlock!
-- fix this code!
*/

分析方法是这样的,我的排查方法是以管道的维度去看, 每一个管道都看什么时候启用,什么时候关闭,什么时候写入,只关注管道,不关注函数功能。结果发现。看起来好像并没有问题,

每个独立的管道是真的没问题

单个没问题 我怀疑应该是整体的问题了 怀疑是 管道数组里面 还在处理 但是管道数组关闭了,但是也有同步 不应该啊

后来经过提醒,是 看一下切片里面放管道有什么要求,引发切片扩容 ?扩容之后是nil

先检查下这段代码,看看wg.Add的时候最终添加了多少 打印出来

fmt.Printf("\n\n\n cs count: %d\n\n\n", len(cs))
wg.Add(len(cs))

打印出来 cs count: 20  
长度是20

所以一种解法是这样的,直接改成数组

package main

import (
    "fmt"
    "sync"
)

func main() {

    in := gen()

    // FAN OUT
    // Multiple functions reading from the same channel until that channel is closed
    // Distribute work across multiple functions (ten goroutines) that all read from in.
    xc := fanOut(in, 10)

    // FAN IN
    // multiplex multiple channels onto a single channel
    // merge the channels from c0 through c9 onto a single channel
    for n := range merge(xc) {
       fmt.Println(n)
    }

}

func gen() <-chan int {
    out := make(chan int)
    go func() {
       for i := 0; i < 10; i++ {
          for j := 3; j < 13; j++ {
             out <- j
          }
       }
       close(out)
    }()
    return out
}

func fanOut(in <-chan int, n int) [10]<-chan int {
    //xc := make([]<-chan int, n)
    var xc [10]<-chan int
    for i := 0; i < n; i++ {
       //xc = append(xc, factorial(in))
       xc[i] = factorial(in)
    }
    return xc
}

func factorial(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
       for n := range in {
          out <- fact(n)
       }
       close(out)
    }()
    return out
}

func fact(n int) int {
    total := 1
    for i := n; i > 0; i-- {
       total *= i
    }
    return total
}

func merge(cs [10]<-chan int) <-chan int {
    var wg sync.WaitGroup
    out := make(chan int)

    output := func(c <-chan int) {
       for n := range c {
          out <- n
       }
       wg.Done()
    }
    fmt.Printf("\n\n\n cs count: %d\n\n\n", len(cs))
    wg.Add(len(cs))
    for _, c := range cs {
       go output(c)
    }

    // Start a goroutine to close out once all the output goroutines are
    // done.  This must start after the wg.Add call.
    go func() {
       wg.Wait()
       close(out)
    }()
    return out
}

/*
CHALLENGE #1:
-- This code throws an error: fatal error: all goroutines are asleep - deadlock!
-- fix this code!
*/

注意数组定义就可以了,不需要初始化,是字典才需要make 显示初始化

但是这里就很烦恼,因为字典我居然在函数定义的时候传形参也要写上个数,同时 ...说是可以拆包,但是我传数组居然没有拆包,所以还有更好的解法

回答疑惑的几个问题,

第一:为什么数组传参的时候,形参也要指定长度:

是的,数组传参的时候也是规定要指定长度的,这就导致很不方便

第二:为什么把写 var out [all_count]chan int 也不行

是的,这个按照规定也不行,数组的长度不允许是变量,一定是固定不变的,除非设置 all_count 为 coonst all_count int 类型才行

第三: 不是说可以拆包吗,为什么拆包 不起作用 ?

拆包是对于切片类型来说的,只有切片才能进行拆包,如果还是切片类型的话,那么拆包是没问题的

在go中,切片的应用还是灵活点,用的比较多,其实像刚才这个情况,直接在初始化的时候写成

var xc []<-chan int

改成切片,不用make的形式,反而就可以了,因为这样切片只会新增一个不会超长

使用切片的append还有一个大坑

a := make([]int, 3)
a = append(a, 1)
a = append(a, 2)
//a = append(a, 3)
fmt.Printf("%T, %v, LEN: %d", a, a, len(a))

这样定义一个切片的时候 ,append两次,切片的长度变成了5, 因为原有的切片没有变。。。只是在后面增加,输出是

[]int, [0 0 0 1 2], LEN: 5

真的是令人困扰的问题啊~~

27 code in process

playing with type

var answer int
answer = 32 / 3.74

这样子会报错,但是其他的类型转化可以,大概就是 等于号左边的无法进行类型转化,最多进行推断

fmt.Println(len("世界"))

这个长度输出的是6,如果你要判断中文的长度,需要转成 rune 切片,或者使用utf8 来计算,比如

runeStr := []rune(str) fmt.Println("字符串中字符的数量:", len(runeStr))
或者
count, _ := utf8.RuneCountInString(str) fmt.Println("字符串中字符的数量:", count)

切片类型不能像Python那要使用负数

全局变量的写法可以是 ,定义的时候就赋值,直接等于号

var a string = "a"
var b int = 2

剩下的貌似不用看了,都是比较细节的了,可以开始全心看框架了第九天就是GolangTraining终结篇了