Golang常见面试题

06-01 1509阅读

文章目录

  • Go 面试问题及答案
    • 基础相关
      • 1. golang 中 make 和 new 的区别?
      • 2. 数组和切片的区别
      • 3. for range 的时候它的地址会发生变化么?
      • 4. go defer 的顺序和返回值修改
      • 5. uint 类型溢出
      • 6. 介绍 rune 类型
      • 7. golang 中解析 tag 和反射原理
      • 8. 调用函数传入结构体时,应该传值还是指针?
      • 9. slice 遇到过哪些坑?
      • 10. go struct 能不能比较?
      • 11. Go 闭包
      • Context 相关
        • 1. context 结构是什么样的?
        • 2. context 使用场景和用途?
        • Channel 相关
          • 1. channel 是否线程安全?
          • 2. go channel 的底层实现原理
          • 3. nil、关闭的 channel、有数据的 channel 的读写行为
          • 4. 向 channel 发送数据和从 channel 读数据的流程
          • Map 相关
            • 1. map 使用注意点,并发安全?
            • 2. map 循环是有序的还是无序的?
            • 3. map 中删除一个 key,它的内存会释放么?
            • 4. 处理 map 并发访问的方案
            • 5. nil map 和空 map 的区别
            • 6. map 的数据结构和扩容
            • 7. map 取一个 key 后修改值,原 map 是否变化?
            • GMP 相关
              • 1. 什么是 GMP?
              • 2. 进程、线程、协程的区别
              • 3. 抢占式调度如何实现?
              • 4. M 和 P 的数量问题
              • 5. 协程怎么退出?
              • 6. map 如何顺序读取?
              • 锁相关
                • 1. 除了 mutex 以外的安全读写共享变量方式
                • 2. Go 如何实现原子操作?
                • 3. Mutex 是悲观锁还是乐观锁?
                • 4. Mutex 有几种模式?
                • 5. goroutine 的自旋占用资源如何解决?
                • 6. 读写锁底层实现
                • 同步原语相关
                  • 1. sync 同步原语
                  • 2. sync.WaitGroup
                  • 并发相关
                    • 1. 控制并发数
                    • 2. 多个 goroutine 对同一个 map 写会 panic,异常是否可以用 defer 捕获?
                    • 3. 如何优雅地实现一个 goroutine 池
                    • 4. select 可以用于什么?
                    • 5. 主协程如何等其余协程完再操作?
                    • GC 相关
                      • 1. go gc 是怎么实现的?
                      • 2. go gc 算法是怎么实现的?
                      • 3. GC 中 STW 时机,各个阶段是如何解决的?
                      • 4. GC 的触发时机
                      • 内存相关
                        • 1. 谈谈内存泄露,什么情况下内存会泄露?
                        • 2. golang 的内存逃逸
                        • 3. Go 的内存分配
                        • 4. 大对象小对象
                        • 5. 堆和栈的区别
                        • 6. 当 go 服务部署到线上发现内存泄露,该怎么处理?
                        • 微服务框架
                          • 1. go-micro 微服务架构怎么实现水平部署的,代码怎么实现?
                          • 2. 怎么做服务发现的
                          • 其他
                            • 1. go 实现单例的方式
                            • 2. 项目中使用 go 遇到的坑
                            • 3. client 如何实现长连接?
                            • 编程题
                              • 1. 3 个函数分别打印 cat、dog、fish,要求每个函数都要起一个 goroutine,按照 cat、dog、fish 顺序打印在屏幕上 100 次。
                              • 2. 如何优雅地实现一个 goroutine 池

                                Go 面试问题及答案

                                基础相关

                                1. golang 中 make 和 new 的区别?

                                • make: 用于初始化切片、map 和 channel。返回的是这些数据结构的引用。make 会分配内存并初始化内存内容。

                                  slice := make([]int, 0)   // 创建一个空的切片
                                  m := make(map[string]int)  // 创建一个空的 map
                                  ch := make(chan int)       // 创建一个 channel
                                  
                                • new: 用于分配内存,并返回指向该类型的指针,且未初始化内存内容。

                                  p := new(int)  // 创建一个指向 int 的指针,初始值为 0
                                  

                                  2. 数组和切片的区别

                                  • 数组: 固定长度,长度是类型的一部分,一旦声明长度不可更改。

                                    var arr [5]int  // 数组长度为 5
                                    
                                  • 切片: 动态长度,可以在运行时增加或减少,切片本质上是指向数组的一段。

                                    slice := []int{1, 2, 3}  // 切片,长度不固定
                                    

                                    3. for range 的时候它的地址会发生变化么?

                                    • 在 for range 循环中,使用的变量地址不会发生变化,循环体内的变量是同一个地址,可能会导致修改值时的问题。

                                      nums := []int{1, 2, 3}
                                      for i := range nums {
                                          fmt.Println(&nums[i])  // 打印每次循环中的地址
                                      }
                                      
                                    • for 循环遍历 slice 的问题: 如果在循环中修改 slice 的内容,可能导致未定义的行为。

                                      4. go defer 的顺序和返回值修改

                                      • defer 的执行顺序: 后进先出(LIFO),即最后一个 defer 的函数会最先执行。

                                        func example() {
                                            defer fmt.Println("First")
                                            defer fmt.Println("Second")
                                        }
                                        // 输出顺序为 "Second" "First"
                                        
                                      • defer 修改返回值: 可以通过命名返回值修改,defer 在函数结束时执行,修改返回值。

                                        func f() (result int) {
                                            defer func() { result++ }()
                                            return 1  // 返回值为 2
                                        }
                                        
                                      • defer recover: 可以捕获异常,recover 需要在 defer 函数中调用,通常用于恢复 panic 状态。

                                        func safeCall() {
                                            defer func() {
                                                if r := recover(); r != nil {
                                                    fmt.Println("Recovered from panic:", r)
                                                }
                                            }()
                                            panic("Something went wrong!")
                                        }
                                        

                                        5. uint 类型溢出

                                        • 当 uint 类型的值超过其最大值时,会从 0 开始重新计数,不会引发 panic。
                                          var u uint8 = 255
                                          u++  // 结果为 0
                                          

                                          6. 介绍 rune 类型

                                          • rune 是 Go 的内建类型,表示一个 Unicode 码点,实际上是 int32 的别名,用于表示字符。
                                            var r rune = 'A'  // 65
                                            

                                            7. golang 中解析 tag 和反射原理

                                            • 反射是通过 reflect 包实现的,可以动态获取类型信息和结构体字段标签(tag)。
                                              type User struct {
                                                  Name string `json:"name"`
                                              }
                                              t := reflect.TypeOf(User{})
                                              field, _ := t.Field(0)  // 获取第一个字段的信息
                                              tag := field.Tag.Get("json")  // 获取 tag 的值
                                              

                                              8. 调用函数传入结构体时,应该传值还是指针?

                                              • 一般来说,对于大结构体,使用指针可以避免拷贝,提高性能;对于小结构体,传值通常足够。
                                                type User struct {
                                                    Name string
                                                }
                                                func updateUser(u *User) {
                                                    u.Name = "Updated"
                                                }
                                                

                                                9. slice 遇到过哪些坑?

                                                • 当对 slice 进行扩展时,底层数组可能会重新分配,导致原 slice 的引用失效。
                                                  s1 := []int{1, 2, 3}
                                                  s2 := append(s1, 4)  // 如果扩展,则 s1 可能与 s2 指向不同的底层数组
                                                  

                                                  10. go struct 能不能比较?

                                                  • 如果结构体中所有字段都是可比较的(如没有包含 slice、map、function),那么可以直接比较。
                                                    type Point struct {
                                                        X, Y int
                                                    }
                                                    p1 := Point{1, 2}
                                                    p2 := Point{1, 2}
                                                    fmt.Println(p1 == p2)  // true
                                                    

                                                    11. Go 闭包

                                                    • 闭包是指能够捕获其外部变量的函数,即使外部函数已返回,闭包仍然可以访问这些变量。
                                                      func makeCounter() func() int {
                                                          count := 0
                                                          return func() int {
                                                              count++
                                                              return count
                                                          }
                                                      }
                                                      counter := makeCounter()
                                                      fmt.Println(counter())  // 1
                                                      fmt.Println(counter())  // 2
                                                      

                                                      Context 相关

                                                      1. context 结构是什么样的?

                                                      • context 是一个携带超时、取消信号、请求范围内数据的上下文,常用于控制 goroutine 的生命周期。
                                                        ctx, cancel := context.WithCancel(context.Background())
                                                        defer cancel()
                                                        

                                                        2. context 使用场景和用途?

                                                        • 常用于 API 请求的超时控制、取消信号的传递、请求范围内数据的传递,避免 goroutine 泄露。

                                                          Channel 相关

                                                          1. channel 是否线程安全?

                                                          • 是的,channel 是线程安全的,可以在多个 goroutine 中安全地读写。

                                                            2. go channel 的底层实现原理

                                                            • Channel 基于循环队列和互斥锁实现,支持并发安全的读写。

                                                              3. nil、关闭的 channel、有数据的 channel 的读写行为

                                                              • 读 nil 的 channel 会阻塞。
                                                              • 写 nil 的 channel 会导致 panic。
                                                              • 读关闭的 channel 会返回零值。
                                                              • 写关闭的 channel 会导致 panic。
                                                              • 从空的 channel 读取会阻塞。

                                                                4. 向 channel 发送数据和从 channel 读数据的流程

                                                                • 发送数据时,若 channel 已满则阻塞,若有协程正在等待接收则立即传递。
                                                                • 读取数据时,若 channel 为空则阻塞,若有数据则立即接收。

                                                                  Map 相关

                                                                  1. map 使用注意点,并发安全?

                                                                  • Go 的 map 不是并发安全的,使用 sync.Mutex 或 sync.Map 来保证安全。

                                                                    2. map 循环是有序的还是无序的?

                                                                    • map 的循环是无序的,每次遍历的顺序可能不同。

                                                                      3. map 中删除一个 key,它的内存会释放么?

                                                                      • 是的,删除 key 后会释放内存。

                                                                        4. 处理 map 并发访问的方案

                                                                        • 使用 sync.Map 或在读写 map 时加锁来处理并发访问。

                                                                          5. nil map 和空 map 的区别

                                                                          • nil map 不能插入数据,空 map 可以进行插入和读取。

                                                                            6. map 的数据结构和扩容

                                                                            • map 是基于哈希表实现的,扩容时会创建更大的哈希表并重新分配键值对。

                                                                              7. map 取一个 key 后修改值,原 map 是否变化?

                                                                              • 是的,修改取出的值会影响 map 中的值,前提是通过引用修改。

                                                                                GMP 相关

                                                                                1. 什么是 GMP?

                                                                                • GMP 是 Go 的协程调度模型,包括 Goroutine(G)、机器线程(M)和 P(处理器)。调度器根据 G 和 M 的数量动态分配。

                                                                                  2. 进程、线程、协程的区别

                                                                                  • 进程: 操作系统的基本运行单位,拥有独立的内存空间。
                                                                                  • 线程: 进程内的执行流,多个线程共享同一进程的内存。
                                                                                  • 协程: 用户态轻量级线程,由 Go 运行时调度。

                                                                                    3. 抢占式调度如何实现?

                                                                                    • Go 使用抢占式调度定期检查正在运行的 goroutine,如果运行超时,则

                                                                                      将其挂起,切换到其他 goroutine。

                                                                                      4. M 和 P 的数量问题

                                                                                      • M 的数量代表操作系统线程的数量,P 的数量代表 Go 运行时处理 goroutines 的数量,通常 P 的数量设置为可用 CPU 核心数。

                                                                                        5. 协程怎么退出?

                                                                                        • 协程可以通过 return 语句结束,或者通过 context 的取消信号终止。

                                                                                          6. map 如何顺序读取?

                                                                                          • Go 的 map 无法保证顺序读取,可以使用 slice 结构来保存 key 的顺序。

                                                                                            锁相关

                                                                                            1. 除了 mutex 以外的安全读写共享变量方式

                                                                                            • 使用 sync.RWMutex 读写锁、sync.Once 进行单次初始化等。

                                                                                              2. Go 如何实现原子操作?

                                                                                              • 使用 sync/atomic 包提供的函数,如 atomic.AddInt32。

                                                                                                3. Mutex 是悲观锁还是乐观锁?

                                                                                                • Mutex 是悲观锁,常用于需要保证互斥访问的场景。悲观锁在操作前就假设会发生冲突,乐观锁则假设不会发生冲突。

                                                                                                  4. Mutex 有几种模式?

                                                                                                  • Lock 和 Unlock 方法用于加锁和解锁。
                                                                                                  • 可以使用 RLock 和 RUnlock 实现读写锁。

                                                                                                    5. goroutine 的自旋占用资源如何解决?

                                                                                                    • 自旋锁(spinlock)可以避免上下文切换,但可能导致 CPU 占用高。通过合理的锁设计和使用 sync.Mutex 来控制。

                                                                                                      6. 读写锁底层实现

                                                                                                      • 读写锁通过计数器实现对读操作的并发支持,写操作时需要独占锁。

                                                                                                        同步原语相关

                                                                                                        1. sync 同步原语

                                                                                                        • sync.Mutex: 互斥锁
                                                                                                        • sync.WaitGroup: 等待一组 goroutine 结束
                                                                                                        • sync.Once: 只执行一次的函数

                                                                                                          2. sync.WaitGroup

                                                                                                          • 用于等待一组 goroutine 完成,调用 Add(n) 方法增加计数,Done() 减少计数,Wait() 阻塞直到计数为 0。

                                                                                                            并发相关

                                                                                                            1. 控制并发数

                                                                                                            • 使用带缓冲的 channel 来限制并发数,或者使用 sync.WaitGroup 进行控制。

                                                                                                              2. 多个 goroutine 对同一个 map 写会 panic,异常是否可以用 defer 捕获?

                                                                                                              • 会 panic,但可以用 defer recover() 捕获。

                                                                                                                3. 如何优雅地实现一个 goroutine 池

                                                                                                                • 使用 channel 来限制 goroutine 的数量。
                                                                                                                  type WorkerPool struct {
                                                                                                                      jobs    chan Job
                                                                                                                      wg      sync.WaitGroup
                                                                                                                  }
                                                                                                                  func (wp *WorkerPool) Start(n int) {
                                                                                                                      for i := 0; i  
                                                                                                                  

                                                                                                                  4. select 可以用于什么?

                                                                                                                  • 在多个 channel 上等待,非阻塞的读取和写入,支持超时机制。

                                                                                                                    5. 主协程如何等其余协程完再操作?

                                                                                                                    • 使用 sync.WaitGroup 来等待所有协程完成。

                                                                                                                      GC 相关

                                                                                                                      1. go gc 是怎么实现的?

                                                                                                                      • Go 的垃圾回收使用标记-清扫算法,分为三阶段:标记、清理和压缩。

                                                                                                                        2. go gc 算法是怎么实现的?

                                                                                                                        • 采用三色标记法,分为白色、灰色和黑色对象,黑色对象代表已访问,灰色对象为待访问。

                                                                                                                          3. GC 中 STW 时机,各个阶段是如何解决的?

                                                                                                                          • STW(Stop The World)发生在标记阶段,GC 在此时暂停所有 goroutine。

                                                                                                                            4. GC 的触发时机

                                                                                                                            • 内存使用达到一定阈值时、手动调用 runtime.GC() 时。

                                                                                                                              内存相关

                                                                                                                              1. 谈谈内存泄露,什么情况下内存会泄露?

                                                                                                                              • 常见于 goroutine 泄露、未关闭的 channel 或 map。可以使用 pprof 工具进行定位。

                                                                                                                                2. golang 的内存逃逸

                                                                                                                                • 内存逃逸是指变量在堆上分配而非栈上,通常发生在闭包、函数返回值等场景。

                                                                                                                                  3. Go 的内存分配

                                                                                                                                  • 使用 runtime.Malloc 和 runtime.Free 管理堆内存,局部变量在栈上分配。

                                                                                                                                    4. 大对象小对象

                                                                                                                                    • 小对象多了会导致 GC 压力,因为 GC 需要频繁运行来回收小对象。

                                                                                                                                      5. 堆和栈的区别

                                                                                                                                      • 栈: 由操作系统管理,速度快,生命周期短。
                                                                                                                                      • 堆: 由 Go 运行时管理,适合动态分配,但速度相对慢。

                                                                                                                                        6. 当 go 服务部署到线上发现内存泄露,该怎么处理?

                                                                                                                                        • 通过 pprof 工具进行性能分析,找到内存泄露点,使用 runtime.GC() 手动触发 GC。

                                                                                                                                          微服务框架

                                                                                                                                          1. go-micro 微服务架构怎么实现水平部署的,代码怎么实现?

                                                                                                                                          • 使用服务注册与发现机制,通过 HTTP/gRPC 进行服务间通信,代码示例使用 go-micro 框架。

                                                                                                                                            2. 怎么做服务发现的

                                                                                                                                            • 使用 etcd、Consul 等工具实现服务注册与发现,保持服务的健康状态。

                                                                                                                                              其他

                                                                                                                                              1. go 实现单例的方式

                                                                                                                                              • 使用 sync.Once 确保单例的安全创建。
                                                                                                                                                var instance *Singleton
                                                                                                                                                var once sync.Once
                                                                                                                                                func GetInstance() *Singleton {
                                                                                                                                                    once.Do(func() {
                                                                                                                                                        instance = &Singleton{}
                                                                                                                                                    })
                                                                                                                                                    return instance
                                                                                                                                                }
                                                                                                                                                

                                                                                                                                                2. 项目中使用 go 遇到的坑

                                                                                                                                                • 包管理、协程泄漏、channel 使用不当等。

                                                                                                                                                  3. client 如何实现长连接?

                                                                                                                                                  • 使用 net 包建立 TCP 连接,并保持连接活跃。
                                                                                                                                                    conn, err := net.Dial("tcp", "server:port")
                                                                                                                                                    if err != nil {
                                                                                                                                                        log.Fatal(err)
                                                                                                                                                    }
                                                                                                                                                    defer conn.Close()
                                                                                                                                                    

                                                                                                                                                    编程题

                                                                                                                                                    1. 3 个函数分别打印 cat、dog、fish,要求每个函数都要起一个 goroutine,按照 cat、dog、fish 顺序打印在屏幕上 100 次。

                                                                                                                                                    package main
                                                                                                                                                    import (
                                                                                                                                                        "fmt"
                                                                                                                                                        "sync"
                                                                                                                                                    )
                                                                                                                                                    func main() {
                                                                                                                                                        var wg sync.WaitGroup
                                                                                                                                                        chCat := make(chan struct{})
                                                                                                                                                        chDog := make(chan struct{})
                                                                                                                                                        wg.Add(3)
                                                                                                                                                        go func() {
                                                                                                                                                            defer wg.Done()
                                                                                                                                                            for i := 0; i 
免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

目录[+]

取消
微信二维码
微信二维码
支付宝二维码