美文网首页互联网大厂面试
星媛面试-进大厂必备--Go语言十问(已上岸,贡献给学弟学妹)

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

作者: CStart | 来源:发表于2021-05-19 23:11 被阅读0次

    一,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: <nil>

    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 <nil>

    recover  has error

    recover first  <nil>

    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语言十问(已上岸,贡献给学弟学妹)

        本文链接:https://www.haomeiwen.com/subject/fkvmjltx.html