21天速成go-第八天

60 阅读5分钟

先对之前的7天进行复盘

以后每7天复盘一次

第一天

定义新结构和alias的区别 type a int 是定义新接口 type a = int 是alias

第二天

数组和切片的初始化需要注意,两者初始化都是非常类似的,区别就是数组指定了长度

a := []int{1, 2, 3}
b := [3]int{1, 2, 3}

同样的声明方式,a 就是切片,而不是数组,在第七天写的并发求累加和的程序中,本意是想创建一个切片,但是可能没有写数量,创建的实际是数组

具体是什么类型可以通过 reflect.TypeOf(var).Kind() 来输出,记得最后加上 Kind

字典的初始化要么开始的时候就给值,要么的话,定义完了以后,一定要make 才能开辟空间,否则定义完了以后是nil 不make的话就不能存储,没有开辟空间

m := map[int]int{
   1: 1,
   2: 2,
}
fmt.Printf("m=%v", m)

var m2 map[int]int
m2 = make(map[int]int)
m2[2] = 2
fmt.Printf("m=%v", m)

var m2 map[int]int 返回的是一个nil, 注意var 定义的时候不要加上 =

第三天

关于管道,其实如果不是一直读取的话,是不会报错的,基本的如这个

package main

import "fmt"

func main() {
   done := make(chan bool)

   go gosomething(done)

   <-done

}

func gosomething(done chan bool) {
   for i := 0; i < 10; i++ {
      fmt.Println(i)
   }
   done <- true
}

这个可以正常运行,然后吧 done := make(chan bool,1000) 也能正常运行,因为反正管道也就接受一次 但是如果改成循环的话,就是会出错了

func main() {
   //done := make(chan bool, 100)
   done := make(chan bool, 1)
   go func() {
      for i := 0; i < 10; i++ {
         fmt.Println(i)
      }
      done <- true
   }()
   for n := range done {
      fmt.Printf("done received: %d", n)
   }
}

不管管道是不是同步的都会出错了,所以存在循环的时候就要关闭,避免接收方一直读取

func main() {
   done := make(chan bool, 100)
   //done := make(chan bool, 1)
   go func() {
      defer close(done)
      for i := 0; i < 10; i++ {
         fmt.Println(i)
      }
      done <- true
   }()
   for n := range done {
      fmt.Printf("done received: %v", n)
   }
}

这样才是对的

所以会不会报死锁,关键还是看程序里面是怎么读取的,不过只要能关闭就关闭肯定是最好的 写的100协程求累加和的,也是因为指定了次数,不会造成 空余的等待所以才没有出错

全局变量,只能预先定义,不能初始化,初始化要在函数内部运行,只能先 var 变量名 T 的形式

你如果只需要用管道卡主,不关心读取什么值的话,写 _ 是直接等于不需要 := 的

for _ = range done {

}

写 _ = 反而是错的,而且如果是别的变量名的话,还要求你强制使用,用 _ 就没这种要求了

第四天

关于什么时候用 := 什么时候用 var : 是对 var 的一种写法,如果需要省略var的话,那么就需要 = ,否则就是直接等于

空接口就是容器:接口可以包含方法,但是这个空接口就是不包含任何方法,可以认为是一个非常通用的容器,可以持有任意类型

但是你要同时储存多个话,那就要数组了

func main() {
   a := make([]interface{}, 10)
   a[0] = "aaa"
   a[1] = 2
   fmt.Printf("%v\n", a)
}

打印出来是什么类型,使用 %T

第五天

第六天

第七天

对于结构体的类型就是 a.(int) 强制转化 这个用法是断言,不是强制转化

为什么要类型断言

image.png

突然发现还有一种同步的写法是这样子

func main() {
   c := make(chan int, 10)

   go func() {
      for i := 0; i < 10; i++ {
         c <- i
         fmt.Printf("send: %d\n", i)
      }
   }()

   time.Sleep(time.Duration(3 * time.Second))

   go func() {
      for n := range c {
         fmt.Printf("rec: %d\n", n)
      }
   }()

   close(c)

   for n := range c {
      fmt.Printf("main rec: %d\n", n)
   }
}

通过再开一个协程来接受done ,所以第7天搞得那个 200个斐波拉契计算的,其实也可以用这个


done := make(chan bool, 200)

// 并发放入管道
for i := 1; i <= 100; i++ {
   x := i
   //wg.Add(1)
   go func(j int) {
      in <- j
      //wg.Done()
      done <- true
   }(x)
}
// 这里就确保发送方全部发完了
//wg.Wait()
//close(in)

go func() {
   defer close(in)
   for i := 1; i <= 100; i++ {
      <-done
   }
}()

大概是这样的效果,但是下面的out 管道也是要加上这个done的,总归写起来还是灭有wg 来的清晰

这段代码也有问题,但是没一眼看出来

func main() {

   c := make(chan int)
   done := make(chan bool)

   go func() {
      for i := 0; i < 10; i++ {
         c <- i
      }
      done <- true
   }()

   go func() {
      for i := 0; i < 10; i++ {
         c <- i
      }
      done <- true
   }()

   // we block here until done <- true
   <-done
   <-done
   close(c)

   // to unblock above
   // we need to take values off of chan c here
   // but we never get here, because we're blocked above
   for n := range c {
      fmt.Println(n)
   }
}

因为没有缓冲区,写入写入不了,done 也会一直done 不了,直接什么都不输出的死锁,然后被发现了

只读只写的管道,应该是针对使用方来的,创建者即便是只读管道也是可以写入,应该是这么表述: 只读管道,是收回了写入的权限,只能由创建方写入,避免被别人写入

另外如果要标记一个只读只写的话,那么就别使用命名返回值了,不然怕难以操作

循环中避免变量被覆盖有两种办法:

  1. 循环遍历当做参数传入,然后函数接受的变量起一个别的名字
  2. 创建一个和循环遍历同名的变量

举例如下:

var wg sync.WaitGroup

func main() {
   for i := 0; i < 10; i++ {
      wg.Add(1)
      go func() {
         fmt.Printf("i: %d\n", i)
         wg.Done()
      }()
   }
   wg.Wait()
}

这个输出的全部是 6 和 10 ,都是最后的变量值

第二种改法是:

func main() {
   for i := 0; i < 10; i++ {
      wg.Add(1)
      i := i
      go func() {
         fmt.Printf("i: %d\n", i)
         wg.Done()
      }()
   }
   wg.Wait()
}

同名变量i 居然不行 ? 可以的,这样是可以的,是我看错了,确认是可以的,之前认为不可以是看错了

第一种改法是

func main() {
   for i := 0; i < 10; i++ {
      wg.Add(1)
      go func(v int) {
         fmt.Printf("i: %d\n", v)
         wg.Done()
      }(i)
   }
   wg.Wait()
}

这样传参也是可以的

注意第二种写法一定要是 := 创建一个新的变量,如果是直接 x = i 的话,还是取得循环变量i的地址

结束,看完了,貌似灭有其他的要做笔记的了 。。。。

回看老的,不去写新的,没有新鲜感 真的是折磨人啊。。。好难坚持看以前的笔记