Golang面试题目-常见语法问题(三)

1、写出下例代码输出内容,并说明原因。

package mainimport (  "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 mainimport (  "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 mainimport (  "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 mainimport "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 mainimport "fmt"func main() {  slice := make([]int, 5)  slice = append(slice, 1, 2, 3)  fmt.Println(slice)}

输出内容如下:

解析:make在初始化切片的时候就指定了切片的长度,而append则是在切片最后一位开始追加内容。

6、指出下例代码的问题。

package mainimport "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 mainimport "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))}

解析:上述代码会编译失败。

  1. 定义peo变量代码错误,因为值类型Student{}未实现接口People的方法,所以就不能定义为People类型。

  2. Go中Student和*Student是两种类型,前者表示Student本身,后者是指向Student的地址。

9、写出下例代码输出内容,并说明原因。

package mainimport "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。

扫码关注公众号,获取更多内容。  

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

luyaran

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值