星媛面试-进大厂必备--Go语言十问(已上岸,贡献给学弟学妹)

一,golang语言的数组与切片的区别

答:数组是一个固定长度的基本数据结构类型,slice是基于数组实现的一种长度可变的数据结构常用类型

具体区别如下:

1,创建方式不同,数组如abc := [...]int{1, 2, 3, 4},slice如abc:=[]int{1, 2, 3,4} 或者 abc:=make([]int, 4, 10)

2,传递方式不同,数组是值传递,slice是引用传递

3,底层结构不同,数组基本数据类型,slice header的底层结构如下

type SliceHeader struct {

    Data uintptr

    Len  int

    Cap  int

}

其中SliceHeader是slice的运行时表示形式

参考:https://golang.org/ref/spec#Array_types

https://blog.golang.org/slices-intro

https://golang.org/pkg/reflect/#SliceHeader

二,golang的map类型与C++的map类型区别

1, golang的map类型,仅仅是一个哈希表,具体结构如下:

// A header for a Go map.

type hmap struct {

// Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.

// Make sure this stays in sync with the compiler's definition.

count    int // # live cells == size of map.  Must be first (used by len() builtin)

flags    uint8

B        uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)

noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details

hash0    uint32 // hash seed

buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.

oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing

nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)

extra *mapextra // optional fields

}

参考:https://golang.org/src/runtime/map.go

其中特性如下:

申请必须make,或者使用:=方式,直接使用未初始化的map会panic;

引用类型;

key的类型必须是可比较类型;

并发使用不安全,需要加sync.RWMutex锁;

不指定迭代顺序,若需要顺序,则需要另外申请slice保存keys,然后sort.Ints(keys);

参考:https://blog.golang.org/maps

2,C++的map的底层结构是红黑树

三,close通道关闭之后,Println输出之后的值,是否可以写入

1,close通道关闭之后,执行fmt.Println(<- ch),若ch还有值,则输出ch的值,否则输出ch类型的默认值

2,若写入close的通道,则会panic,报错:panic: send on closed channel

四,简述chan类型

1,贯穿go语言的设计思想:不要通过共享内存来通信,而应该通过通信来共享内存。

2,类似一个先进先出队列;其中buf是一个循环链表结构;lock是互斥锁,所以线程安全;

3,分为无缓冲通道和有缓存通道,无缓冲通道必须先有消费协程,然后再可发送数据;

4,关闭chan,使用close,关闭之后不可再写入,但可以消费chan中的数据。

五,defer/panic/recover/return的理解

1,defer延迟执行

2.1 在函数return之前可以执行的一段代码,常用于资源释放,解锁,增加recover等操作。

2.2 多个defer执行顺序LIFO

例子:

package main

import (

"fmt"

"errors"

)

func main() {

  fmt.Printf("with named param, x: %d\n", namedParam())

  fmt.Printf("error from err1: %v\n", err1())

}

func namedParam() (x int) {

  x = 1

  defer func() { x = 2 }()

  return x

}

func err1() error {

  var err error

  defer func() {

      if r := recover(); r != nil {

        err = errors.New("recovered")

      }

  }()

  panic(`foo`)

  return err

}

输出:

with named param, x: 2

error from err1:

2,panic恐慌

2.1 内置函数,出现致命错误可以使用panic退出程序,如:panic("has error")

3,recover恢复

3.1 内置函数,重新获得panicking协程的控制,防止程序崩溃。注意像fatal的错误无法捕获,如map的并发读写:fatal error: concurrent map read and map write;死锁:fatal error: all goroutines are asleep - deadlock(申请无缓冲chan,直接生产数据会报此致命错误)

举例如下:

package main

import (

"fmt"

)

func main() {

defer fmt.Println("recover first ", recover())

defer func() {

if r := recover(); r != nil {

fmt.Println("recover ", r)

}

}()

defer fmt.Println("recover second ", recover())

panic("has error")

}

输出:

recover second

recover  has error

recover first 

4,return函数返回

4.1 return操作不是原子操作,过程具体可以分为三步:

首先赋值,把return的值赋给返回值

其次检查defer操作,有则调用,所以defer里面的函数是可以更改返回值的

最后返回返回值

六,简述内存逃逸,遇没有遇见过内存溢出及内存泄漏

1,内存逃逸

1.1 golang的内存分配逃逸于栈和堆

1.2 查看逃逸分析日志命令:go build -gcflags=-m

1.3 逃逸场景:指针逃逸,返回局部变量指针;大对象逃逸,申请局部变量是大对象的时候;动态类型,申请不确定大小内存的局部变量逃逸;闭包引用对象逃逸;

1.4 分析内存逃逸好处:可减少gc的压力,不逃逸则函数结束可以回收清理,不需要gc标识清理过程(所以指针传递不一定比值传递效率高);栈上分配内存效率更高(不是绝对,栈也会扩容缩容);静态分析,编译时完成(不影响性能);

1.5 举个例子如下:

package main

import "fmt"

func fibo() func() int {

a, b := 0, 1

fmt.Println("first a, b", a, b)

return func() int {

a, b = b, a+b

return a

}

}

func main() {

f := fibo()

for i := 0; i < 6; i++ {

fmt.Printf("fib: %d\n", f())

}

f1 := fibo()

fmt.Printf("fib1: %d\n", f1())

fmt.Println("f:%p", &f, ", f1:%p", &f1)

}

运行输出:

first a, b 0 1

fib: 1

fib: 1

fib: 2

fib: 3

fib: 5

fib: 8

first a, b 0 1

fib1: 1

f:%p 0xc00000e028 , f1:%p 0xc00000e038

另外可以分析逃逸情况,输入命令:go build -gcflags=-m

输出如下:

# test/t_memory_escape

./main.go:9:13: inlining call to fmt.Println

./main.go:11:9: can inline fibo.func1

./main.go:27:13: inlining call to fmt.Printf

./main.go:33:12: inlining call to fmt.Printf

./main.go:35:13: inlining call to fmt.Println

./main.go:7:2: moved to heap: a

./main.go:7:5: moved to heap: b

./main.go:9:14: "first a, b" escapes to heap

./main.go:9:14: a escapes to heap

./main.go:9:14: b escapes to heap

可以看见a/b俩变量都逃逸了

2,内存溢出

2.1 golang的内存溢出基本没有见过,因为他的堆栈信息是可以扩容的,初始栈的容量是2k。

3,内存泄漏

3.1 golang的内存管理是使用gc机制自动化管理,使用方法是标记清理法,具体到三色标记法,另外使用屏障等技术提高回收效率。

3.2 golang是存在内存泄漏的,如文件/套接字等句柄没有关闭释放。这个没有释放的句柄会堆积在内存,gc并不会回收他们,导致内存泄漏。也有可能是启动的goroutine没有按预期退出,导致协程泄漏。

七,有没有使用过pprof/Benchmark工具

1,pprof工具

1.1 pprof工具是golang自带的性能分析神器

1.2 查看堆栈信息:go tool pprof http://localhost:6060/debug/pprof/heap

1.3 查看cpu信息:go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30

1.4 查看goroutine信息:go tool pprof http://localhost:6060/debug/pprof/goroutine

1.5 查看trace路径信息:go tool pprof http://localhost:6060/debug/pprof/trace

2,Benchmark工具

1.1 性能测试工具

1.2 基准测试,需要_test.go结尾

八,简述golang语言的GMP机制

1,G是goroutine的缩写,用来表示协程;M是machine的缩写,用来表示线程,可以设置最大数量:SetMaxThreads;P是processor的缩写,用来表示逻辑处理器,p的个数配置GOMAXPROCS;

2,p队列有两种,一种全局队列,平衡多个p队列之间的任务,有锁;一种本地队列,无数据竞争,速度快,无锁;

3,启动方式,首先确定p的个数,若有设置环境变量$GOMAXPROCS或者是runtime.GOMAXPROCS()则按设置数量,否则使用默认值cpu核心数。其次动态调整m的数量,若没有足够的数量的m关联p中可运行的g的时候会新创m,如所有的m被阻塞时。

4,调度设计策略,首先当m无g可运行时,会优先从其他p本地队列盗取g来运行,本地队列没有,才会盗取全局队列;其次当本地m运行的g,因进行系统调用而阻塞时,m会释放绑定的p,p会转移到其他空闲的m上运行其他的g;最后go1.14及之后,属于基于信号的抢占式调度,一个g不能无限占用cpu时间,占用一定时间是可以被抢占的,防止其他g饿死;

参考:https://learnku.com/articles/41728


九,简述golang语言的gc机制

1,golang的gc(garbage collection)机制,使用方法是标记清理法,具体到三色标记法,另外使用屏障等技术提高回收效率。

十,简述golang语言cgo原理

1,cgo主要是用来创建go语言包调用c代码,详细可见:https://golang.org/pkg/cmd/cgo/

你可能感兴趣的:(星媛面试-进大厂必备--Go语言十问(已上岸,贡献给学弟学妹))