1. channel
1.1 关闭有缓冲数据的 channel, 还能读取吗(可以)
只有当channel无数据,且channel被close了,才会返回ok=false。 只要有堆积数据,即使 close() 也不会返回关闭状态。
关闭后有数据也能从里面得到数据,除非消耗空了。
1 | func main() { |
1.2 channel 被close后,还能被接收或 select 吗(可以读,不能写)
如果有缓存,值能被收到,ok 是 true
如果无缓存,值是0,ok 是 false,但不会 panic
关闭后, 就是你可以读, 但是不能写
1.3 如何判断channel已经关闭了 ?
Go 中没有一个內建函数去检测管道是否已经被关闭。
- 直接读取channel结构hchan的closed字段, 不安全
1 | import ( |
正常做法是要监听 close()的 channel, 收到通知
如果您不知道通道是否关闭并且盲目地写入该通道,则说明您的程序设计不良。 重新设计它,使其在关闭后无法写入。
可用 bool值,但是会多读一个值
1
2
3
4value, ok := <- channel
if !ok {
// channel was closed and drained
}
1.4 channel 的零值
channel的零值是nil。也许会让你觉得比较奇怪,nil的channel有时候也是有一些用处的。
- 因为对一个nil的channel发送和接收操作会永远阻塞
- 在select语句中操作nil的channel永远都不会被select到。这使得我们可以用nil来激活或者禁用case。
1 | func main() { |
1.5 优雅的关闭 channel
https://www.liuvv.com/p/8b210700.html
1.6 什么时候用 channel, 什么时候用Mutex
https://github.com/golang/go/wiki/MutexOrChannel
channel的能力是让数据流动起来,擅长的是数据流动的场景
传递数据的所有权,即把某个数据发送给其他协程
分发任务,每个任务都是一个数据
交流异步结果,结果是一个数据
mutex的能力是数据不动,某段时间只给一个协程访问数据的权限擅长数据位置固定的场景
缓存
状态
2. map
2.1 map 为什么无序
map扩容, key 会移动
map 在扩容后,会发生 key 的搬迁,原来落在同一个 bucket 中的 key,搬迁后,有些 key 就要远走高飞了(bucket 序号加上了 2^B)。而遍历的过程,就是按顺序遍历 bucket,同时按顺序遍历 bucket 中的 key。搬迁后,key 的位置发生了重大的变化,有些 key 飞上高枝,有些 key 则原地不动。这样,遍历 map 的结果就不可能按原来的顺序了。
底层代码随机值遍历
Go 做得更绝,当我们在遍历 map 时,并不是固定地从 0 号 bucket 开始遍历,每次都是从一个随机值序号的 bucket 开始遍历,并且是从这个 bucket 的一个随机序号的 cell 开始遍历。这样,即使你是一个写死的 map,仅仅只是遍历它,也不太可能会返回一个固定序列的 key/value 对了。
2.2 map 的key 可以是什么类型
map中的key可以是任何的类型,只要它的值能比较是否相等. Go语言里是无法重载操作符的
布尔值
数字
字符串
指针
Pointer values are comparable. Two pointer values are equal if they point to the same variable or if both have value nil. Pointers to distinct zero-size variables may or may not be equal.
当指针指向同一变量,或同为nil时指针相等,但指针指向不同的零值时可能不相等。
Channel
Channel values are comparable. Two channel values are equal if they were created by the same call to make or if both have value nil.
Channel当指向同一个make创建的或同为nil时才相等。
Interface
Interface values are comparable. Two interface values are equal if they have identical dynamic types and equal dynamic values or if both have value nil.
当接口有相同的动态类型并且有相同的动态值,或者值为都为nil时相等。
Struct
Struct values are comparable if all their fields are comparable. Two struct values are equal if their corresponding non-blank fields are equal.
结构体当所有字段的值相同,并且没有相应的非空白字段时,则他们相等。
只包含上述类型的数组。
Array values are comparable if values of the array element type are comparable. Two array values are equal if their corresponding elements are equal.
两个数组只要他们包括的元素,每个元素的值相同,则他们相等。
但不能是:
- slice
- map
- function
2.3 map 如何有序遍历
map 默认是无序的,不管是按照 key 还是按照 value 默认都不排序。
有序遍历
如果你想为 map 排序,需要将 key(或者 value)拷贝到一个切片,再对切片排序(使用 sort 包),然后可以使用切片的 for-range 方法打印出所有的 key 和 value。
2.4 slice 和 map 并发安全吗
map和slice都是并发不安全的, 解决方案:
- 加锁
- 使用 channel 串行化
- sync.Map
3. interface
3.1 interface 当参数时的坑, 传递 nil 也不是 nil
如果需要判断, 请用反射 reflect.ValueOf(i).IsNil()
1 | package main |
4. sync.once原理
内部使用了锁和原子包,修改一个标识解决的。
1 | func (o *Once) Do(f func()) { |
6. make
6.1 make时传递的数字参数
1 | package main |
如果传递了个数, 就是有默认值了
1 | package main |
6.2 make 和 new的区别
- make 只能用于 slice,map,channel,返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型是引用类型。
new
的作用是为类型申请一片内存空间,并返回指向这片内存的指针,同时把分配的内存置为该类型的零值(a pointer to a newly allocated zero value of that type)
7. 函数
7.1 函数参数传递,是值还是引用
Go 中函数传参仅有值传递一种方式, 只有slice, map, channel 本身是引用类型, 所以可以改
其实传递的就是值,但是为什么能改内容呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38func ChangeMap(value map[string]string) {
fmt.Printf("map内部 %p\n", &value)
value["age"] = "30"
}
func ChangeSlice(value []string) {
fmt.Printf("slice内部 %p\n", &value)
value[0] = "haha"
}
func main() {
map1 := make(map[string]string)
map1["age"] = "21"
fmt.Printf("map外部 %p\n", &map1)
fmt.Println(map1["age"])
ChangeMap(map1)
fmt.Println(map1["age"])
slice1 := make([]string, 0)
slice1 = append(slice1, "hehe")
fmt.Println(slice1)
fmt.Printf("slice外部 %p\n", &slice1)
ChangeSlice(slice1)
fmt.Println(slice1)
}
/*
map外部 0xc000092018
21
map内部 0xc000092028
30
[hehe]
slice外部 0xc00008a040
slice内部 0xc00008a080
[haha]
*/普通的类型就是值传递
1
2
3
4
5
6
7
8
9
10
11
12
13package main
import "fmt"
func main() {
a := 10
fmt.Printf("%#v\n", &a) // (*int)(0xc420018080)
vFoo(a)
}
func vFoo(b int) {
fmt.Printf("%#v\n", &b) // (*int)(0xc420018090)
}
8. 其他问题
8.1 字节和字节对齐
字节数
64位系统下, int 默认 int64 , 8个字节,
float 64, 8个字节
1 | package main |
- 字节对齐(根据操作系统的位数对齐的)
1 | package main |
8.2 非运行多态
go 语言中,当子类调用父类方法时,“作用域”将进入父类的作用域,看不见子类的方法存在
1 | package main |