1、下面代码能运行吗?为什么。
type Param map[string]interface{}type Show struct {Param
}func main1() {s := new(Show)s.Param["RMB"] = 10000
}
解析
共发现两个问题:
main
函数不能加数字。new
关键字无法初始化Show
结构体中的Param
属性,所以直接对s.Param
操作会出错。
2、请说出下面代码存在什么问题。
type student struct {Name string
}func zhoujielun(v interface{}) {switch msg := v.(type) {case *student, student:msg.Name}
}
解析:
golang中有规定,switch type
的case T1
,类型列表只有一个,那么v := m.(type)
中的v
的类型就是T1类型。
如果是case T1, T2
,类型列表中有多个,那v
的类型还是多对应接口的类型,也就是m
的类型。
所以这里msg
的类型还是interface{}
,所以他没有Name
这个字段,编译阶段就会报错。具体解释见: https://golang.org/ref/spec#Type_switches
3、写出打印的结果。
type People struct {name string `json:"name"`
}func main() {js := `{"name":"11"}`var p Peopleerr := json.Unmarshal([]byte(js), &p)if err != nil {fmt.Println("err: ", err)return}fmt.Println("people: ", p)
}
解析:
按照 golang 的语法,小写开头的方法、属性或 struct
是私有的,同样,在json
解码或转码的时候也无法上线私有属性的转换。
题目中是无法正常得到People
的name
值的。而且,私有属性name
也不应该加json
的标签。
4、下面的代码是有问题的,请说明原因。
type People struct {Name string
}func (p *People) String() string {return fmt.Sprintf("print: %v", p)
}func main() {p := &People{}p.String()
}
解析:
在golang中String() string
方法实际上是实现了String
的接口的,该接口定义在fmt/print.go
中:
type Stringer interface {String() string
}
在使用 fmt
包中的打印方法时,如果类型实现了这个接口,会直接调用。而题目中打印 p
的时候会直接调用 p
实现的 String()
方法,然后就产生了循环调用。
5、请找出下面代码的问题所在。
func main() {ch := make(chan int, 1000)go func() {for i := 0; i < 10; i++ {ch <- i}}()go func() {for {a, ok := <-chif !ok {fmt.Println("close")return}fmt.Println("a: ", a)}}()close(ch)fmt.Println("ok")time.Sleep(time.Second * 100)
}
解析:
在 golang 中 goroutine
的调度时间是不确定的,在题目中,第一个写 channel
的 goroutine
可能还未调用,或已调用但没有写完时直接 close
管道,可能导致写失败,既然出现 panic
错误。
6、请说明下面代码书写是否正确。
var value int32func SetValue(delta int32) {for {v := valueif atomic.CompareAndSwapInt32(&value, v, (v+delta)) {break}}
}
解析:
atomic.CompareAndSwapInt32
函数不需要循环调用。
7、下面的程序运行后为什么会爆异常。
type Project struct{}func (p *Project) deferError() {if err := recover(); err != nil {fmt.Println("recover: ", err)}
}func (p *Project) exec(msgchan chan interface{}) {for msg := range msgchan {m := msg.(int)fmt.Println("msg: ", m)}
}func (p *Project) run(msgchan chan interface{}) {for {defer p.deferError()go p.exec(msgchan)time.Sleep(time.Second * 2)}
}func (p *Project) Main() {a := make(chan interface{}, 100)go p.run(a)go func() {for {a <- "1"time.Sleep(time.Second)}}()time.Sleep(time.Second * 100000000000000)
}func main() {p := new(Project)p.Main()
}
解析:
有一下几个问题:
time.Sleep
的参数数值太大,超过了1<<63 - 1
的限制。defer p.deferError()
需要在协程开始出调用,否则无法捕获panic
。
8、请说出下面代码哪里写错了
func main() {abc := make(chan int, 1000)for i := 0; i < 10; i++ {abc <- i}go func() {for {a := <-abcfmt.Println("a: ", a)}}()close(abc)fmt.Println("close")time.Sleep(time.Second * 100)
}
解析:
协程可能还未启动,管道就关闭了。
9、请说出下面代码,执行时为什么会报错
type Student struct {name string
}func main() {m := map[string]Student{"people": {"zhoujielun"}}m["people"].name = "wuyanzu"
}
解析:
map的value本身是不可寻址的,因为map中的值会在内存中移动,并且旧的指针地址在map改变时会变得无效。故如果需要修改map值,可以将map
中的非指针类型value
,修改为指针类型,比如使用map[string]*Student
.
10、请说出下面的代码存在什么问题?
type query func(string) stringfunc exec(name string, vs ...query) string {ch := make(chan string)fn := func(i int) {ch <- vs[i](name)}for i, _ := range vs {go fn(i)}return <-ch
}func main() {ret := exec("111", func(n string) string {return n + "func1"}, func(n string) string {return n + "func2"}, func(n string) string {return n + "func3"}, func(n string) string {return n + "func4"})fmt.Println(ret)
}
解析:
依据4个goroutine的启动后执行效率,很可能打印111func4,但其他的111func*也可能先执行,exec只会返回一条信息。
11、下面这段代码为什么会卡死?
package mainimport ("fmt""runtime"
)func main() {var i bytego func() {for i = 0; i <= 255; i++ {}}()fmt.Println("Dropping mic")// Yield execution to force executing other goroutinesruntime.Gosched()runtime.GC()fmt.Println("Done")
}
解析:
Golang 中,byte 其实被 alias 到 uint8 上了。所以上面的 for 循环会始终成立,因为 i++ 到 i=255 的时候会溢出,i <= 255 一定成立。
也即是, for 循环永远无法退出,所以上面的代码其实可以等价于这样:
go func() {for {}
}
正在被执行的 goroutine 发生以下情况时让出当前 goroutine 的执行权,并调度后面的 goroutine 执行:
- IO 操作
- Channel 阻塞
- system call
- 运行较长时间
如果一个 goroutine 执行时间太长,scheduler 会在其 G 对象上打上一个标志( preempt),当这个 goroutine 内部发生函数调用的时候,会先主动检查这个标志,如果为 true 则会让出执行权。
main 函数里启动的 goroutine 其实是一个没有 IO 阻塞、没有 Channel 阻塞、没有 system call、没有函数调用的死循环。
也就是,它无法主动让出自己的执行权,即使已经执行很长时间,scheduler 已经标志了 preempt。
而 golang 的 GC 动作是需要所有正在运行 goroutine
都停止后进行的。因此,程序会卡在 runtime.GC()
等待所有协程退出。