语言规范

报错

subslice引用

以下代码会打印什么结果
报错

短声明

在局部变量x已申明,y未声明的情况下,以下哪些写法是正确的

x, _ := f()
x, _ = f()
x, y := f()
x, y = f()
                        

x, _ := f() //incorrect
x, _ = f() //correct
x, y := f() //correct
x, y = f() //incorrect
                            
报错

interface是否等于nil的判断

以下代码会打印什么结果,并解释为什么 println(InitType() == nil) 有语法错误
报错

map存取数据

在以下AB两行修改代码,用正确的用法存取map数据

package main

func main() {
	m := make(map[string]int)
	m["a"] = 1
	if v, ok := m["b"]; ok {
		println(v)
	}
}

                            
报错

指针基本操作

在以下AB两行补全代码,使运行结果打印"foo"

package main

type S struct {
    m string
}

func f() *S {
    return &S{"foo"} //A
}

func main() {
    p := *f()  //B
    print(p.m) //print "foo"
}
                            
报错

interface{}万能指针

在以下ABCD四行里,哪几行有语法错误

package main

type S struct {
}

func f(x interface{}) {
}

func g(x *interface{}) {
}

func main() {
    s := S{}
    p := &s
    f(s) //A correct
    g(s) //B incorrect
    f(p) //C correct
    g(p) //D incorrect
}
                            
报错

临时变量的指针

解释为何以下代码打印map的值都为3,并在A行附近修改代码,使打印结果为012的序列

package main

const N = 3

func main() {
	m := make(map[int]*int)

	for i := 0; i < N; i++ {
		j := int(i)
		m[i] = &j
	}

	for _, v := range m {
		print(*v)
	}
}

                            
报错

break外层循环

修改以下代码,使程序只打印0,0后就break到最外层的for

package main

func main() {
outer:
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			print(i, ",", j, " ")
			break outer
		}
		println()
	}
}

                            
报错

全局变量

假设g为全局变量,以下哪些写法是正确的

var g
var g G
var g = G{}
g := G{}
                        

var g //incorrect
var g G //correct
var g = G{} //correct
g := G{} //incorrect
                            
报错

defer栈

纠正下面代码的一个错误

package main

import (
	"io/ioutil"
	"os"
)

func main() {
	f, err := os.Open("file")
	if err != nil {
		return
	}
	defer f.Close()

	b, err := ioutil.ReadAll(f)
	println(string(b))
}

                            
报错

panic栈

以下代码会打印什么结果
报错

recover

以下代码会打印什么结果,以下代码的运行后exit code是0还是1
报错

goroutine闭包参数

修改以下代码,使打印的map的长度为N=10

package main

import (
	"sync"
)

const N = 10

func main() {
	m := make(map[int]int)

	wg := &sync.WaitGroup{}
	mu := &sync.Mutex{}
	wg.Add(N)
	for i := 0; i < N; i++ {
		go func(i int) {
			defer wg.Done()
			mu.Lock()
			m[i] = i
			mu.Unlock()
		}(i)
	}
	wg.Wait()
	println(len(m))
}

                            
报错

receiver函数的"仿重载"

以下代码会打印什么结果
报错

string转换为bytes

以下代码会打印什么结果
报错

map并发操作的临界区

修改以下代码,使避免concurrent map writes错误

package main

import (
	"math/rand"
	"sync"
)

const N = 10

func main() {
	m := make(map[int]int)

	wg := &sync.WaitGroup{}
	mu := &sync.Mutex{}
	wg.Add(N)
	for i := 0; i < N; i++ {
		go func() {
			defer wg.Done()
			mu.Lock()
			m[rand.Int()] = rand.Int()
			mu.Unlock()
		}()
	}
	wg.Wait()
	println(len(m))
}

                            
报错

深度比较

解释以下代码打印的结果为什么是false,并修改A行使打印x和y的值是否相等的结果

package main

import (
	"fmt"
	"reflect"
)

type S struct {
	a, b, c string
}

func main() {
	x := interface{}(&S{"a", "b", "c"})
	y := interface{}(&S{"a", "b", "c"})
	fmt.Println(reflect.DeepEqual(x, y))
}

                            
报错

goroutine让先

在A行增加一行代码,使打印的小写字母是顺序的

package main

import (
	"fmt"
	"runtime"
	"sync"
)

const N = 26

func main() {
	const GOMAXPROCS = 1
	runtime.GOMAXPROCS(GOMAXPROCS)

	var wg sync.WaitGroup
	wg.Add(2 * N)
	for i := 0; i < N; i++ {
		go func(i int) {
			defer wg.Done()
			runtime.Gosched()
			fmt.Printf("%c", 'a'+i)
		}(i)
		go func(i int) {
			defer wg.Done()
			fmt.Printf("%c", 'A'+i)
		}(i)
	}
	wg.Wait()
}

                            
报错

map的值的内部字段可修改性

修改以下代码,使map中值对象的字段可修改

package main

type S struct {
	name string
}

func main() {
	m := map[string]*S{"x": &S{"one"}}
	m["x"].name = "two"
}

                            
报错

Struct的Slice的排序

在A行添加代码,使打印的s S[]结果按v的值排序

package main

import (
	"fmt"
	"sort"
)

type S struct {
	v int
}

func main() {
	s := []S{{1}, {3}, {5}, {2}}
	sort.Slice(s, func(i, j int) bool { return s[i].v < s[j].v })
	fmt.Printf("%#v", s)
}

                            

标准库和包

报错

init函数

有ABC三个包以及以下文件,根据它们的依赖关系写出init函数的调用顺序

// A/a1.go
package A
func init() {
    println("a1")
}
var A1 = ""

// A/a2.go
package A
func init() {
	println("a2")
}
var A2 = ""


// B/b1.go
package B
func init() {
	println("b1")
}
var B1 = ""


// B/b2.go
package B
import "github.com/test/A"
func init() {
	println("b2")
}
func f() {
	_ = A.A2
}
var B2 = ""


// C/main.go
package main
import (
	"github.com/test/B"
)
func main() {
	_ = B.B2
}

                        

a1
a2
b1
b2
                            
报错

json反序列化

纠正下面代码的一个错误

package main

import (
	"encoding/json"
	"fmt"
)

type Result struct {
	Status int `json:"status"`
}

func main() {
	var data = []byte(`{"status": 200}`)
	result := &Result{}

	if err := json.Unmarshal(data, result); err != nil {
		fmt.Println("error:", err)
		return
	}

	fmt.Printf("result=%+v", result)
}

                            
报错

map并发存取

修改以下代码AB两行,使变量m可以在goroutine里存取数据,并不会导致concurrent map writes错误

package main

import (
	"sync"
)

const N = 100

func main() {
	m := &sync.Map{}
	wg := &sync.WaitGroup{}
	wg.Add(N)
	for i := 0; i < N; i++ {
		go func(i int) {
			m.Store(i, i)
			wg.Done()
		}(i)
	}
	wg.Wait()
}

                            
报错

utf8字符串长度

修改下面代码以显示正确的utf8字符长度

package main

import (
	"fmt"
	"unicode/utf8"
)

func main() {
	fmt.Println(utf8.RuneCountInString("你好"))
}

                            
报错

正则表达式的高级模式

修改A行中的正则表达式,使s替换所有以0开头的连续(可跨行)的0为一个0,即打印的结果为 100 0 1 0 1

package main

import (
	"fmt"
	"regexp"
)

func main() {
	s := `100
00
0
1
0

0
1`
	pattern := regexp.MustCompile("(?m:^0(0|\n)*0)")
	s = pattern.ReplaceAllString(s, "0")
	fmt.Println(s)
}

                            
报错

文本文件的行遍历

遍历一个文本文件的每一行,以下函数分别有什么区别,哪个最合适 fmt.Fscanf() bufio.Reader.ReadLine() bufio.ReadString('\n') bufio.Scanner.Scan()

bufio.Scanner.Scan(): 最合适
fmt.Fscanf(): 只适用于格式化文本
bufio.Reader.ReadLine(): 底层函数,当行长度超过buffer上限时需要调用多次
bufio.ReadString('\n'): 不能处理EOF
                            
报错

堆容器

以下程序的打印结果是什么
报错

context超时

补全以下A行的代码,使创建一个有2秒timeout的context变量,即最后打印的结果为"waited for 1 sec"

package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	timeout := 2 * time.Second
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()

	select {
	case <-time.After(1 * time.Second):
		fmt.Println("waited for 1 sec")
	case <-ctx.Done():
		fmt.Println(ctx.Err())
	}
}

                            
报错

命令行参数解析

补全init()中AB两行,使变量ip接受命令行参数ip,变量port接受命令行参数port,并使程序在零参数的情况下打印0.0.0.0:8000。

package main

import "flag"
import "fmt"

var ip string
var port int

func init() {
	flag.StringVar(&ip, "ip", "0.0.0.0", "ip address")
	flag.IntVar(&port, "port", 8000, "port number")
}

func main() {
	flag.Parse()
	fmt.Printf("%s:%d", ip, port)
}

                            
报错

文本模版

补全A行的tpl的模版内容,使起能生成最多三层Tree的内容,即最终输出结果为 A B C D

package main

import (
	"log"
	"os"
	"text/template"
)

func main() {
	const tpl = `{{.Name}}{{range $child := .Children}}
 {{$child.Name}}{{range $grandchild := $child.Children}}
  {{$grandchild.Name}}{{end}}{{end}}
` // A

	type Tree struct {
		Name     string
		Children []Tree
	}
	root := Tree{
		"A", []Tree{
			Tree{"B", []Tree{
				Tree{"C", []Tree{}},
			}},
			Tree{"D", []Tree{}},
		},
	}

	t := template.Must(template.New("tree").Parse(tpl))

	err := t.Execute(os.Stdout, root)
	if err != nil {
		log.Fatalf("executing template:", err)
	}

}

                            
报错

html模版

html/template和text/template有什么异同

html/template和text/template的接口完全一致,html/template增加了对于html生成的安全保护机制
                            
报错

http服务

在以下AB两行补全代码,使程序启动一个http服务在8000端口,对所有请求都输出响应hello

package main

import (
	"fmt"
	"net/http"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
		fmt.Fprintf(w, "hello")
	})
	http.ListenAndServe(":8000", mux)
}

                            
报错

sql查询

在以下ABCD四行空缺处补上代码,使程序遍历并打印所有结果,并打印可能的报错

age := 27
rows, err := db.Query("SELECT name FROM users WHERE age=?", age)
if err != nil {
        log.Fatal(err)
}
defer ___ // A
for ___ { //B
        var name string
        if err := ___; err != nil { //C
                log.Fatal(err)
        }
        fmt.Printf("%s is %d\n", name, age)
}
if err := ___; err != nil { //D
        log.Fatal(err)
}
                        

age := 27
rows, err := db.Query("SELECT name FROM users WHERE age=?", age)
if err != nil {
        log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
        var name string
        if err := rows.Scan(&name); err != nil {
                log.Fatal(err)
        }
        fmt.Printf("%s is %d\n", name, age)
}
if err := rows.Err(); err != nil {
        log.Fatal(err)
}
                            

工具链

报错

gcflags参数

解释以下几个gcflags参数的意义

-l:
-m:
-N:
                        

-l:禁止函数inline
-m:显示对象内存从栈向堆的escape分析,以及是否inline的选择
-N:禁止优化
                            
报错

benchmark次数变量

以下代码中b.N是如何调整的

func BenchmarkFib10(b *testing.B) {
	for n := 0; n < b.N; n++ {
		Fib(10)
	}
}
                        

系统以一定倍数的算法自动增大b.N直到benchmark函数运行时间稳定
                            
报错

govendor路径覆盖

govendor是如何实现对GOPATH的覆盖的

govendor使用了vendor目录对GOPATH覆盖,vendor目录是Go 1.6开始引入的一个特性,用来引入独立的包路径以不污染GOPATH和GOROOT
                            
报错

GOMAXPROCS

以下程序会打印什么结果,当A行修改为GOMAXPROCS=2又会有什么变化

GOMAXPROCS=1时会先打印a-z后打印A-Z
GOMAXPROCS=2时打印a-z中会参杂打印A-Z
                            
报错

共享库

1. 将以下代码文件中str.go编译成str.so需要执行什么命令才能使其被build成共享库 2. 补全main.go代码中A行中缺失的部分

// str.go
package main

import "strings"

func UpperCase(s string) string {
	return strings.ToUpper(s)
}


// main.go
package main

import (
	"fmt"
	"log"
	"plugin"
)

func main() {
	p, err := plugin.Open("str.so")
	if err != nil {
		log.Panicf("plugin.Open: %s\n", err)
	}
	f, err := p.Lookup("UpperCase")
	if err != nil {
		log.Panicf("Lookup UpperCase: %s\n", err)
	}
	UpperCase, ok := ___ // A
	if !ok {
		log.Panicf("UpperCase assertion: %s\n", err)
	}
	s := UpperCase("hello")
	fmt.Println(s)
}

                        

go build -buildmode=plugin -o str.so str.go

package main

import (
	"fmt"
	"log"
	"plugin"
)

func main() {
	p, err := plugin.Open("str.so")
	if err != nil {
		log.Panicf("plugin.Open: %s\n", err)
	}
	f, err := p.Lookup("UpperCase")
	if err != nil {
		log.Panicf("Lookup UpperCase: %s\n", err)
	}
	UpperCase, ok := f.(func(string) string)
	if !ok {
		log.Panicf("UpperCase assertion: %s\n", err)
	}
	s := UpperCase("hello")
	fmt.Println(s)
}

                            
报错

GODEBUG参数

解释以下GODEBUG参数的意义 GODEBUG=gctrace=1,schedtrace=1000

在stderr输出gc的debug信息以及每1000ms打印go scheduler的状态
                            
报错

$GOROOT和$GOPATH

GOROOT和GOPATH的区别是什么?

GOROOT是go标准库的根目录,包含标准库的CLI可执行文件,包,源代码,文档等等
GOPATH是第三方库的可执行文件,包和源代码的根目录
                            
报错

go generate

简述go generate的机制

在go generate命令行里指定的目录或者go文件里搜索//go:generate的注释,并以注释中指定的shell命令的stdout替代注释,并将结果输出至stdout
                            
报错

http server的pprof

简述如何使用pprof来监控http server的性能数据

在http server的main.go里加入 import _ "net/http/pprof" 以启动pprof的http接口
使用以下命令来获取相关信息
N秒的 CPU profile:
go tool pprof http://localhost:6060/debug/pprof/profile --second N
heap profile:
go tool pprof http://localhost:6060/debug/pprof/heap
goroutine blocking profile:
go tool pprof http://localhost:6060/debug/pprof/block
                            
报错

./...路径通配符

命令行下./...是什么意思?

当前目录下递归搜索所有路径的通配符
                            

内部原理

报错

string内部结构

在以下A行补全代码,将变量b从[]byte类型转化成string类型并且不产生内存拷贝,并使运行结果打印"143"

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	var b = []byte("123")
	s := *(*string)(unsafe.Pointer(&b))

	b[1] = '4'
	fmt.Printf("%+v\n", s) //print 143
}

                            
报错

slice内部结构

在以下A行补全代码,使printOriginalSlice打印参数subslice的原始slice的值(提示:长度为M = 10)

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

const M = 10
const N = 5

func printOriginalSlice(subslice *[]int) {
	data := (*[M]int)(unsafe.Pointer(((*reflect.SliceHeader)(unsafe.Pointer(subslice))).Data))

	fmt.Printf("original\t%p:%+v\n", data, *data)
}

func main() {
	slice := make([]int, M)
	for i, _ := range slice {
		slice[i] = i
	}
	subslice := slice[0:N]

	fmt.Printf("slice\t%p:%+v\n", &slice, slice)
	fmt.Printf("subslice\t%p:%+v\n", &subslice, subslice)
	printOriginalSlice(&subslice)
}

                            
报错

defer的性能成本

defer有哪些额外的性能成本

runtime.deferproc会导致栈上下文拷贝操作
runtime.deferreturn会导致栈上下文取回操作
                            
报错

map的malloc阈值容量

默认map的malloc阈值容量(在阈值以下不会进行新的malloc)是多少,如何调整

默认为128,通过修改runtime.hashmap的maxKeySize和maxValueSize可以调整
                            
报错

内存分配

runtime.newobject()函数的作用是什么。是否make和new一定会调用runtime.newobject().

runtime.newobject()函数的作用是分配堆内存。当编译器做优化的时候,make和new所在的函数有可能被inline掉,所以不是一定会导致分配堆内存。
                            
报错

SSA

简要介绍SSA(Static Single Assignment)有哪些好处

消除不会被执行的代码
消除冗余变量和逻辑
优化寄存器分配
将变量尽可能优化成常量
将高成本的指令优化成低成本的指令
                            
报错

AST

以下代码会打印什么结果。(提示:打印结果的格式如下,只需在下划线出补全剩余结果) *ast.File: ____: ____ *ast.GenDecl: *ast.ValueSpec: ____: ____ ____: ____
报错

Go引导过程

简述一个go编译出的可执行文件启动的过程

执行src/runtime/目录下平台相关的汇编引导程序
runtime·args():格式化CLI参数
runtime·osinit():初始化CPU core
runtime·schedinit(): 初始化goroutine上限,栈内存,malloc,CLI参数,环境变量,debug参数,gc,GOMAXPROCS
runtime·mstart():启动gc monitor,enable gc,import所有依赖并执行相关init函数,执行main.main()

                            
报错

channel同步异步的区别

同步channel和异步channel的区别是什么?

同步channel和异步channel的区别,在于是否有缓冲槽。
同步模式的关键是找到匹配的接收或发送方,找到则直接拷贝数据,找不到就将自身打包后放入等待队列,由另一方复制数据并唤醒。
异步模式围绕缓冲槽进行。当有空位时,发送者向槽中复制数据;有数据后,接收者从槽中获取数据。双方都有唤醒排队另一方继续工作的责任。
                            
报错

析构函数

go语言中间是否有析构函数?

没有。但是runtime.SetFinalizer可以为一个对象指针指定一个回掉函数。
                            
报错

gc工作机制

简述go gc工作机制

MarkWorker goroutine循环扫描所有对象并使用白灰黑三色分别标记不可达对象,待定对象,可达对象,直至确定所有的可达对象(即没有灰色对象)。
编译器在写操作时特意插入的一段代码即write barrier,以实时监控所有用户goroutine对heap的修改
执行Stop the world: scheduler休眠M(线程)并抢占所有G(goroutine)队列
回收不可达对象以供heap或者central重用
如果有整块的span内存被回收,在一定条件下可以release给操作系统
执行Start the world,唤醒P(cpu core)和M(线程) 继续执行G(goroutine)队列
                            
报错

goroutine休眠

解释C.sleep和time.Sleep的区别

C.sleep直接调用sleep syscall,会导致操作系统生成闲置线程
time.Sleep对goroutine进行了优化,并未使用sleep syscall
                            
报错

内存分配过程

简述go为object分配内存的过程

对于小对象(<=32k),优先从goroutine的cache上分配,其次可以从central上申请,再其次可以从堆内存分配
对于大对象(>32k),直接从堆内存分配
                            
报错

stack和heap内存分配判定

go什么情况下为对象分配栈内存,什么情况下分配堆内存

生命周期只存在在函数stack frame的变量,分配栈内存。跨栈传递对象的引用,对象则escape到堆内存。
大对象(>32k)直接在堆上分配内存。
可以inline的函数里,原本escape到堆内存的对象也可能会被编译器优化成栈内存对象。
                            
报错

goroutine暂停或者终止

有哪些暂停或者终止当前goroutine的方法,它们之间有什么区别

runtime.Gosched: 让出CPU core并重新加入goroutine队列,并稍后会自动继续执行
runtime.gopark: 使goroutine进入等待状态直至参数里的回调函数unlockf返回false才可继续执行
runtime.notesleep: 直接休眠M(线程)
runtime.Goexit:终止G(goroutine),并调用G.defer,并且并不会导致panic