• golang 面试题


    1. golang那些类型是引用类型,那些类型是值类型?

    • 引用类型: 指针,map,slice,channel,interface
    • 值类型: 非引用类型

    Note: 对于interface类型,其原类型是引用类型那interface就也是引用类型,如果其原类型是值类型,那interface就也是值类型,取决与其原类型。

    2. new和make的区别?

    • new可以为任意类型的变量分配内存空间并初始化为对应类型的零值,返回变量的指针。
    • make为map,slice,channel类型分配内存,并进行初始化,返回对应类型的引用。当new为map,slice,channel分配内存时,初始化为对应的零值为nil,当我们操作nil的map,slice或channel时就会因为空指针而panic。

    3.golang切片和数组的区别?

    切片和数组都是用来存储同一类型的数据集合,但是数组是值类型,而切片是引用类型,并且数组不可动态扩展长度,而切片可以自动扩容。切片的底层实现是通过数组来实现的,切片还比数组多了一个cap容量的概念。

    4.切片的扩容方式?

    当所需容量大于当前容量,就会扩容,当所需容量大于当前容量的两倍时就会直接扩到所需容量,当len > 1024的,就会每次扩25%直到扩到满足所需容量为止,当长度小于1024的时候,就每次扩两倍容量。并且所需容量不同类型的计算方式还不一样,但是可以保证计算出来的所需容量一定大于或等于真实的所需容量。一个int32类型的切片,其cap为0,len为0,一次性添加5个数据的之后,这时cap会为6,而如果是int16类型的则会为8,真实所需的容量是5。

    5.切片扩容所引发的问题

    当切片扩容时,会开辟一块新的内存空间,把老地址的切片的内容复制到新地址当中,并让原指针指向新的地址空间。避坑方法:1.使用copy函数,copy一个全新的切片 2.和append一样,去接收所返回的切片

    6. 什么是内存逃逸

    函数中的一个变量,如果其作用域没有超过该函数,那么该函数的内存就会在栈上分配,否则就会在堆上分配。一个变量的内存到底是在栈上分配,还是在堆上分配,取决于编译器做完逃逸分析之后决定的。

    7.什么是深拷贝,什么是浅拷贝

    深拷贝就是完全复制一个新的对象,新对象与原对象的存储空间完全不一样,而浅拷贝就是复制一个指针,指针仍然指向原对象的内存空间。在golang中,当我们调用一个函数,传引用类型的时候或者是指针都是浅拷贝,而传其他类型都是深拷贝。

    8.调用一个函数传值还是传结构体?

    调用一个函数,通常传的是指针,避免深拷贝带来的效率和内存上的消耗,除非我不希望该方法改变我结构体中的内容。接受的话一般接受结构体,避免内存逃逸带来gc上的压力。

    Note: 接收参数,gc压力和深拷贝的开销取一个平衡点,其实怎么说应该都不算错,关键是要说明白你为什么要这么做。

    9.什么是channel?

    channel即管道,是golang的重要核心之一,是golang中协程通信的方式之一。Golang的并发哲学,不要通过内存共享来通信,而是通过通信来实现内存共享,其具体的体现就是channel。传统的mutex锁都是通过共享临界资源区来实现通信,而golang支持通过channel来进行通信,从而实现内存共享。

    10.channel有哪几种

    channel分为两种,带缓冲的channel和不带缓冲的channel

    • make(chan int),这样创建的是不带缓冲的chan
    • make(chan int, 1),这样创建的是带一个缓冲的chan

    11.介绍一下channel的结构体组成(3_2021_11_3)

    type hchan struct {
       qcount   uint           // total data in the queue
       dataqsiz uint           // size of the circular queue
       buf      unsafe.Pointer // points to an array of dataqsiz elements
       elemsize uint16
       closed   uint32
       elemtype *_type // element type
       sendx    uint   // send index
       recvx    uint   // receive index
       recvq    waitq  // list of recv waiters
       sendq    waitq  // list of send waiters
    
       // lock protects all fields in hchan, as well as several
       // fields in sudogs blocked on this channel.
       //
       // Do not change another G's status while holding this lock
       // (in particular, do not ready a G), as this can deadlock
       // with stack shrinking.
       lock mutex
    }

     

    这里需要了解几个重要的属性,一个是buf指向存储数据的数组,sendx发送数据的索引,recvx接收数据的索引,sendq发送数据的等待队列,recvq接收数据的等待队列以及lock

    当一个协程发送数据到带缓冲的channel时,把数据存储在缓冲数组的那个位置由sendx决定。如果缓冲池满了,就会加入到sendq等待队列当中,由接收的协程负责将其唤醒。

    如何保证channel的并发安全,就是使用互斥锁来保证的。

    12.向channel里面发送数据的逻辑

    向channel里面发送数据分为以下几步:

    • 首先看接收区的等待队列是否有正在等待的receiver,如果有的话,则直接把数据发送给receiver,并将其唤醒
    • 然后看是否有缓冲区,如果缓冲区没满的话,则直接把数据放到缓冲区
    • 如果是非阻塞的则直接返回,否则加入到发送区的等待队列当中去

    13.goroutine接收的逻辑是怎么样的?

    goroutine接受的逻辑和发送的逻辑差不多,分为以下几步:

    • 首先查看sendq队列中是否有等待的g,如果有的话,则直接把等待的g中的数据取出,然后将其唤醒,返回即可
    • 然后查看是否有缓冲区,缓冲队列是否有数据,如果有的话则直接从缓冲区中拿取数据
    • 否则查看是否阻塞,如果阻塞则加入到recvq队列当中,否则直接返回

    14.golang的select当有多个goroutine准备就绪,它是如何选择的?

    select语句是用来处理与channel IO相关的逻辑,当有多个channel准备就绪的时候,其是伪随机选择一个goroutine来接收,然后执行相关的语句块。

    Note: select关键字常用于和goroutine超时相关的逻辑设计。

    15.golang什么时候会panic?

    这里总结了8种,应付面试官应该是够了

    • nil空指针异常
    • 数组切片越界
    • 向未初始化的map赋值
    • 并发的写map
    • 关闭未初始化的channel,重复关闭channel,向关闭的channel写数据
    • 死锁,channel只有发送而没有接收
    • interface类型断言失败
    • 递归死循环,堆栈溢出

    16.子协程出现panic能在父协程使用recover()捕获吗?

    不能,只能在子协程内部使用recover()捕获panic,协程只能捕获自己的panic。

    17.什么样的panic不可恢复

    • 并发读写map
    • 递归死循环
    • 死锁

    18.unsafe.Pointer和uintptr是用来干什么的呢?

    在golang中,为了安全性,是不允许指针像C++那样进行类型转换以及计算的,但是有些场景又必须要这么做怎么办呢?于是出现了unsafe.Pointer用于指针类型转换,比如*int64可以转换为*int64,出现了uintptr用于指针运算。

    对于unsafe.Point有以下几点性质:

    • 任意类型的指针都能转化成unsafe.Point
    • unsafe.Point能转化成任意类型的指针
    • unsafe.Point可以转化成uintptr
    • uintptr可以转化成unsafe.Point

    19.常用unsafe.Point和uintptr做什么呢,这么做有什么好处呢?

    unsafe.Point常用于操作结构体的私有变量,以及类型转换。

    好处就是golang中只有unsafe.Point能做到这个事,其他方法都做不到,反射的底层也是用unsafe.Point做的。

    20.unsafe.Point和unintptr有什么坑呢?

    千万要小心,不要为uintptr起一个中间变量,例如这样:

    u := uintptr(unsafe.Pointer(student))
    age := (*int)(unsafe.Pointer(u))

    这是因为当发生gc的时候,可能会修改变量的内存地址,同时也会修改指向该变量的指针指向新的地址。但是uintptr是一个整数,其不是一个指针,因此在gc修改变量的时候,可不会修改它的值,他还指向原来的地址,然后转化成unsafe.Point进行操作,当然会报错。

  • 相关阅读:
    软件定义网络实验4:Open vSwitch 实验——Mininet 中使用 OVS 命令(实验过程及结果记录)
    软件定义网络实验3:测量路径的损耗率 (实验过程及结果记录)
    第一次个人编程作业
    软件定义网络实验2:Mininet拓扑的命令脚本生成(实验过程及结果记录)
    软件定义网络实验1:Mininet源码安装和可视化拓扑工具(实验过程及结果记录)
    第一次博客作业
    第07组(69) 需求分析报告
    第七组(69)团队展示
    第三次作业
    结对编程作业
  • 原文地址:https://www.cnblogs.com/peteremperor/p/16788392.html
Copyright © 2020-2023  润新知