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_.runnextif !_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 consumerst := _p_.runqtailif 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 consumptionreturn}if runqputslow(_p_, gp, h, t) {return}// the queue is not full, now the put above must succeedgoto 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 <- 1stringChan <- "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 + bfmt.Println(index, a, b, ret)return ret}func main() {a := 1b := 2defer calc("1", a, calc("10", a, b))a = 0defer 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]intsync.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))}
解析:上述代码会编译失败。
-
定义peo变量代码错误,因为值类型Student{}未实现接口People的方法,所以就不能定义为People类型。
-
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 *Studentreturn stu}func main() {if live() == nil {fmt.Println("A")} else {fmt.Println("B")}}
解析:上述代码会输出内容【B】。
*Student定以后本身没有初始化的值,所以*Student是nil的,但*Student实现了People接口,接口不为nil。
扫码关注公众号,获取更多内容。

本文通过多个实战案例,深入解析了Go语言中的goroutine调度、方法调用、通道使用、defer执行顺序等核心概念,并探讨了并发安全及接口使用等问题。
1780

被折叠的 条评论
为什么被折叠?



