先对之前的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) 强制转化 这个用法是断言,不是强制转化
为什么要类型断言
突然发现还有一种同步的写法是这样子
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 不了,直接什么都不输出的死锁,然后被发现了
只读只写的管道,应该是针对使用方来的,创建者即便是只读管道也是可以写入,应该是这么表述: 只读管道,是收回了写入的权限,只能由创建方写入,避免被别人写入
另外如果要标记一个只读只写的话,那么就别使用命名返回值了,不然怕难以操作
循环中避免变量被覆盖有两种办法:
- 循环遍历当做参数传入,然后函数接受的变量起一个别的名字
- 创建一个和循环遍历同名的变量
举例如下:
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的地址
结束,看完了,貌似灭有其他的要做笔记的了 。。。。
回看老的,不去写新的,没有新鲜感 真的是折磨人啊。。。好难坚持看以前的笔记