1、写出下例代码输出内容,并说明原因。
package main
import (
"fmt"
"runtime"
"sync"
)
func main() {
runtime.GOMAXPROCS(1)
wg := sync.WaitGroup{}
wg.Add(20)
for i := 0; i < 10; i++ {
go func() {
fmt.Println("i: ", i)
wg.Done()
}()
}
for i := 0; i < 10; i++ {
go func(i int) {
fmt.Println("i: ", i)
wg.Done()
}(i)
}
wg.Wait()
}
输出内容如下:
解析:上述代码输出内容决定于调度器优先调度那个goroutine,从runtime源码中可以看到,当创建一个goroutine时,会优先放到下一个调度的runnext字段上作为下一次优先调度的goroutine,因此最先进行输出的是最后一次创建的goroutine,也就是第二个迭代中最后一个创建的goroutine,内容为【i: 9】。
runtime部分源码如下:
func newproc(siz int32, fn *funcval) {
argp := add(unsafe.Pointer(&fn), sys.PtrSize)
gp := getg()
pc := getcallerpc()
systemstack(func() {
newg := newproc1(fn, argp, siz, gp, pc)
_p_ := getg().m.p.ptr()
runqput(_p_, newg, true)
if mainStarted {
wakep()
}
})
}
.......
// runqput tries to put g on the local runnable queue.
// If next is false, runqput adds g to the tail of the runnable queue.
// If next is true, runqput puts g in the _p_.runnext slot.
// If the run queue is full, runnext puts g on the global queue.
// Executed only by the owner P.
func runqput(_p_ *p, gp *g, next bool) {
if randomizeScheduler && next && fastrand()%2 == 0 {
next = false
}
if next {
retryNext:
oldnext := _p_.runnext
if !_p_.runnext.cas(oldnext, guintptr(unsafe.Pointer(gp))) {
goto retryNext
}
if oldnext == 0 {
return
}
// Kick the old runnext out to the regular run queue.
gp = oldnext.ptr()
}
retry:
h := atomic.LoadAcq(&_p_.runqhead) // load-acquire, synchronize with consumers
t := _p_.runqtail
if t-h < uint32(len(_p_.runq)) {
_p_.runq[t%uint32(len(_p_.runq))].set(gp)
atomic.StoreRel(&_p_.runqtail, t+1) // store-release, makes the item available for consumption
return
}
if runqputslow(_p_, gp, h, t) {
return
}
// the queue is not full, now the put above must succeed
goto retry
}
2、写出下例代码输出内容。
package main
import (
"fmt"
)
type People struct{}
func (p *People) ShowA() {
fmt.Println("showA")
p.ShowB()
}
func (p *People) ShowB() {
fmt.Println("showB")
}
type Teacher struct {
People
}
func (t *Teacher) ShowB() {
fmt.Println("teacher showB")
}
func main() {
t := Teacher{}
t.ShowA()
}
输出内容如下:
解析:Go没有继承的概念,只有组合,也没有虚方法,更没有重载,因此,*Teacher的showB不会覆写被组合的People方法。
3、确认下例代码是否会发生异常,详细说明。
package main
import (
"fmt"
"runtime"
)
func main() {
runtime.GOMAXPROCS(1)
intChan := make(chan int, 1)
stringChan := make(chan string, 1)
intChan <- 1
stringChan <- "1"
select {
case value := <-intChan:
fmt.Println(value)
case value := <-stringChan:
panic(value)
}
}
解析:执行结果是随机的,Go在多个case可读时会公平选中其中一个执行。
4、写出下例代码输出内容。
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
}
输出结果如下:
解析:defer在定义的时候会计算好调用函数的参数,所以会优先输出10、20这两个参数,之后就是根据定义的顺序倒序执行。
5、写出下例代码输出内容。
package main
import "fmt"
func main() {
slice := make([]int, 5)
slice = append(slice, 1, 2, 3)
fmt.Println(slice)
}
输出内容如下:
解析:make在初始化切片的时候就指定了切片的长度,而append则是在切片最后一位开始追加内容。
6、指出下例代码的问题。
package main
import "sync"
type UserAges struct {
ages map[string]int
sync.Mutex
}
func (ua *UserAges) Add(name string, age int) {
ua.Lock()
defer ua.Unlock()
ua.ages[name] = age
}
func (ua *UserAges) Get(name string) int {
if age, ok := ua.ages[name]; ok {
return age
}
return -1
}
解析:上述代码在执行Get方法的时候可能会被panic。
虽然UserAges结构体中有使用sync.Mutex做写锁,但是map的并发读写操作是不安全的。因为map属于引用类型,并发读写时多个协程是通过指针访问同一地址,也就是访问共享变量,这个时候同时读写存在资源竞争关系,会报【fatal error: concurrent map read and map write】错误。因此在Get中也需要加锁,因为只进行读操作,所以可以用读写锁sync.RWMutex。
7、下例代码中的迭代有什么问题?
func (set *threadSafeSet) Iter() <-chan interface{} {
ch := make(chan interface{})
go func() {
set.RLock()
for elem := range set.s {
ch <- elem
}
close(ch)
set.RUnlock()
}()
return ch
}
解析:make初始化channel时,默认是无缓冲的,所以上述迭代中的问题是在迭代过程中写入channel的时候会产生阻塞。
8、下例代码是否可编译?说明原因。
package main
import "fmt"
type People interface {
Speak(string) string
}
type Student struct{}
func (stu *Student) Speak(think string) (talk string) {
if think == "1" {
talk = "1"
} else {
talk = "2"
}
return
}
func main() {
var peo People = Student{}
think := "1"
fmt.Println(peo.Speak(think))
}
解析:上述代码会编译失败。
-
定义peo变量代码错误,因为值类型Student{}未实现接口People的方法,所以就不能定义为People类型。
-
Go中Student和*Student是两种类型,前者表示Student本身,后者是指向Student的地址。
9、写出下例代码输出内容,并说明原因。
package main
import "fmt"
type People interface {
Show()
}
type Student struct{}
func (stu *Student) Show() {
}
func live() People {
var stu *Student
return stu
}
func main() {
if live() == nil {
fmt.Println("A")
} else {
fmt.Println("B")
}
}
解析:上述代码会输出内容【B】。
*Student定以后本身没有初始化的值,所以*Student是nil的,但*Student实现了People接口,接口不为nil。
扫码关注公众号,获取更多内容。