语言规范
报错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