百度网盘git
提取码:qhhv github
1.什么是goroutine,他与process, thread有什么区别?golang
2. 什么是channel,为何它能够作到线程安全?面试
3. 了解读写锁吗,原理是什么样的,为何能够作到?数组
4. 如何用channel实现一个令牌桶?安全
5. 如何调试一个go程序?app
6. 如何写单元测试和基准测试?ide
7. goroutine 的调度是怎样的?函数
8. golang 的内存回收是如何作到的?单元测试
9. cap和len分别获取的是什么?
10. netgo,cgo有什么区别?
11. 什么是interface?
面试的时候尽可能了解协程,线程,进程的区别。
明白channel是经过注册相关goroutine id实现消息通知的。
slice底层是数组,保存了len,capacity和对数组的引用。
若是了解协程的模型,就知道所谓抢占式goroutine调用是什么意思。
尽可能了解互斥锁,读写锁,死锁等一些数据竞争的概念,debug的时候可能会有用。
尽可能了解golang的内存模型,知道多小才是小对象,为何小对象多了会形成gc压力。
一、写出下面代码的输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package
main
import
"fmt"
func
main() {
defer_all()
panic(
"触发异常"
)
}
func
defer_all() {
defer
func
() {
fmt.Println(
"打印前"
)
}()
defer
func
() {
fmt.Println(
"打印中"
)
}()
defer
func
() {
fmt.Println(
"打印后"
)
}()
}
|
解析:这道题主要考察的是对 defer 的理解,defer 主要是延迟函数,延迟到调用者函数执行 return 命令以前,
多个 defer 以前按照先进后出的顺序执行,因此,这道题中,在 panic 触发时结束函数运行,在 return 以前依次打
印:打印后、打印中、打印前。最后 runtime 运行时抛出打印 panic 异常信息,panic 须要 defer 结束后才会向上传
递
须要注意的是,函数的 return value 不是原子操做,而是在编译器中被分解成两部分:返回值和return,而咱们
知道 defer 是在 return 以前执行的,因此能够在 defer 函数中修改返回值,以下示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package
main
import
(
"fmt"
)
func
main() {
fmt.Println(doubleScore(0))
//0
fmt.Println(doubleScore(20.0))
//40
fmt.Println(doubleScore(50.0))
//50
}
func
doubleScore(source float32) (score float32) {
defer
func
() {
if
score < 1 || score >= 100 {
//将影响返回值
score = source
}
}()
score = source * 2
return
//或者
//return source * 2
}
|
2. 下面的代码输出什么?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
package
main
import
"fmt"
func
calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return
ret
}
func
main() {
a := 1
b := 2
defer
calc(
"1"
, a, calc(
"10"
, a, b))
a = 0
defer
calc(
"2"
, a, calc(
"20"
, a, b))
b = 1
}
|
解析:
程序在执行到第三行的时候,会先执行 calc 函数的 b 参数,即:calc("10",a,b),输出:10,1,2,3 获得值 3,而后由于
defer 定义的函数是延迟函数故 calc("1",1,3) 会被延迟执行
程序执行到第五行的时候,一样先执行 calc("20",a,b) 输出:20,0,2,2 获得值 2,一样将 calc("2",0,2) 延迟执行
程序执行到末尾的时候,按照栈先进后出的方式依次执行:calc("2",0,2),calc("1",1,3),则就依次输出:2,0,2,二、
1,1,3,4
3.请写出如下输出内容
1
2
3
4
5
|
func
main() {
s := make([]int, 5)
s = append(s,1,2,3)
fmt.Println(s)
}
|
解析:
使用 make 初始化 slice,第二个参数表明的是 slice 的长度,slice 还有第三个参数表示容量,这里没有指定容量表示建立一个
满容的切片,使用 len()、cap() 函数获取切片的 长度,初始化后切片的长度和容量都是 5,使用 append 追加三个元素使得切片的
长度大于原有的容量,此时切片的容量扩大一倍,变成 10,所以输出的结果为:
1
|
[0 0 0 0 0 1 2 3]
|
3. 下面的代码能正常编译吗?
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
|
package
main
import
(
"fmt"
)
type
People
interface
{
Speak(string) string
}
type
Stduent
struct
{}
func
(stu *Stduent) Speak(think string) (talk string) {
if
think ==
"bitch"
{
talk =
"You are a good boy"
}
else
{
talk =
"hi"
}
return
}
func
main() {
var
peo People = Stduent{}
think :=
"bitch"
fmt.Println(peo.Speak(think))
}
|
运行打印结果:
1
2
3
|
# command-line-arguments
.\main.
go
:23:6: cannot use Stduent literal (
type
Stduent) as
type
People in assignment:
Stduent does not implement People (Speak method has pointer receiver)
|
从上面的输出信息能够看出 Student 没有实现 People 这个接口
解析:
咱们来看一下语言规范里面定义的规则,这些规则用来讲明一个类型的值或指针是否实现了该接口:
1.类型 *T 的可调用方法集包含接收者为 *T 或 T 的全部方法集
这条规则说的是若是咱们用来调用接口方法的变量是一个指针类型,那么方法的接收者能够是值类型也能够是指针类型,
如今看一下咱们的例子显然是不符合规则的,var peo People = Student{} 是一个值类型
2. 类型 T 的可调用方法集包含接收者为 T 的全部方法集
这条规则说的是若是咱们用来调用接口方法的变量是一个值类型,那么方法的接收者必需要是值类型才能够被调用,看一下
咱们的例子,方法的接收者是指针类型
上面的代码能够这样修改:
1.var peo People = &Student{}
2.将方法的接收者改为值类型
能够参考这篇文章:https://github.com/Unknwon/gcblog/blob/master/content/26-methods-interfaces-and-embedded-types-in-golang.md
4. 下面的代码是死循环吗?
1
2
3
4
5
6
|
func main() {
v := []int{1, 2, 3}
for
i := range v {
v = append(v, i)
}
}
|
解析:
直接运行上面的代码咱们会发现上面的程序不会出现死循环,可以正常结束。
咱们来看一下切片的 for range ,它的底层代码是:
1
2
3
4
5
6
7
8
|
// for_temp := range
// len_temp := len(for_temp)
// for index_temp = 0; index_temp < len_temp; index_temp++ {
// value_temp = for_temp[index_temp]
// index = index_temp
// value = value_temp
// original body
// }
|
从底层代码中咱们能够看到在遍历 slice 以前会先计算 slice 的长度做为循环次数,循环体中,每次循环会先获取
元素值,若是 for-range 中接收 index,value 则会对 index,value 进行一次赋值
因为循环开始前循环次数已经肯定了,因此循环过程当中新添加的元素没办法遍历到
参考文章:https://my.oschina.net/renhc/blog/2396058
5.下面的代码有什么问题吗?
1
2
3
4
5
6
7
8
9
10
|
slice := []int{0, 1, 2, 3}
myMap := make(map[int]*int)
for
index, value := range slice {
myMap[index] = &value
}
fmt.Println(
"=====new map====="
)
for
k, v := range myMap {
fmt.Printf(
"%d => %d\n"
, k, *v)
}
|
运行打印输出结果:
1
2
3
4
5
|
=====
new
map=====
3 => 3
0 => 3
1 => 3
2 => 3
|
结果彻底同样,都是最后一次遍历的值。经过第 4 道题目对切片 for-range 的底层代码可知,遍历后
的值赋值给 value,在咱们的例子中,会把 value 的地址保存到 myMap 中,这里的 value 是一个全局变量,
&value 取得是这个全局变量的地址,因此最后输出的结果都是同样的而且是最后一个值,至关于以下代码:
1
2
3
4
5
6
7
8
9
|
// for_temp := range
// len_temp := len(for_temp)
// for index_temp = 0;index_temp < len_temp;index_temp++ {
// value_temp = for_temp[index_temp]
// index = index_temp
// value = value_temp
// myMap[index] = &value
// original body
// }
|
注意:这里必须是保存指针才会有问题,若是直接保存的是 value,不会有问题
总结:经过 for-range 遍历切片,首先,计算遍历的次数(切片的长度);每次遍历,都会把当前遍历到的值
存放到一个全局变量中