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. 数据结构
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
5. interface
5.1 interface 当参数时的坑, 传递 nil 也不是 nil
如果需要判断, 请用反射 reflect.ValueOf(i).IsNil()
1 | package main |
6. make
6.1 make时传递的数字参数
1 | package main |
如果传递了个数, 就是有默认值了
1 | package main |
6.2 make 和 new的区别
make 只能用于 slice,map,channel
返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型是引用类型。
new(T) 返回 T 的指针 *T 并指向 T 的零值。
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 |
8.3 互斥锁正常模式和饥饿模式
在Go一共可以分为两种抢锁的模式,一种是正常模式,另外一种是饥饿模式。
当一个G1持有着一个锁的时候,G2会自旋的去尝试获取这个锁,当自旋超过4次还没有能获取到锁的时候,这个G2就会被加入到获取锁的等待队列里面,并阻塞等待唤醒。
正常模式下:所有等待锁的 goroutine 按照 FIFO(先进先出)顺序等待。唤醒的goroutine 不会直接拥有锁,而是会和新请求锁的 goroutine 竞争锁。新请求锁的 goroutine 具有优势:它正在 CPU 上执行,而且可能有好几个,所以刚刚唤醒的 goroutine 有很大可能在锁竞争中失败,长时间获取不到锁,就会切换到饥饿模式。
饥饿模式下:直接由unlock把锁交给等待队列中排在第一位的G,同时,饥饿模式下,新进来的G不会参与抢锁也不会进入自旋状态(禁用自旋),会直接进入等待队列的尾部排队。
9. 引用和传值
Go语言是没有引用传递的, Go里只有传值(值传递)。
slice / map / chan 是golang的3个引用类型, 本质上 它本身/或者它的一个成员 是指针
map
把 map本身想象成指针, 看指针的值不一样
1 | func main() { |
Slice
通过下标可以直接修改, 但是 append 指针变了, 需要传出来才可以
1 | func main() { |