目录

Golang常见面试题

文章目录

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

Go 面试问题及答案

基础相关

1. golang 中 make 和 new 的区别?

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

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

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

2. 数组和切片的区别

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

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

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

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

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

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

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

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

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

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

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

5. uint 类型溢出

  • uint 类型的值超过其最大值时,会从 0 开始重新计数,不会引发 panic。

    go 复制代码
    var u uint8 = 255
    u++  // 结果为 0

6. 介绍 rune 类型

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

    go 复制代码
    var r rune = 'A'  // 65

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

  • 反射是通过 reflect 包实现的,可以动态获取类型信息和结构体字段标签(tag)。

    go 复制代码
    type User struct {
        Name string `json:"name"`
    }
    t := reflect.TypeOf(User{})
    field, _ := t.Field(0)  // 获取第一个字段的信息
    tag := field.Tag.Get("json")  // 获取 tag 的值

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

  • 一般来说,对于大结构体,使用指针可以避免拷贝,提高性能;对于小结构体,传值通常足够。

    go 复制代码
    type User struct {
        Name string
    }
    
    func updateUser(u *User) {
        u.Name = "Updated"
    }

9. slice 遇到过哪些坑?

  • 当对 slice 进行扩展时,底层数组可能会重新分配,导致原 slice 的引用失效。

    go 复制代码
    s1 := []int{1, 2, 3}
    s2 := append(s1, 4)  // 如果扩展,则 s1 可能与 s2 指向不同的底层数组

10. go struct 能不能比较?

  • 如果结构体中所有字段都是可比较的(如没有包含 slice、map、function),那么可以直接比较。

    go 复制代码
    type Point struct {
        X, Y int
    }
    
    p1 := Point{1, 2}
    p2 := Point{1, 2}
    fmt.Println(p1 == p2)  // true

11. Go 闭包

  • 闭包是指能够捕获其外部变量的函数,即使外部函数已返回,闭包仍然可以访问这些变量。

    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 的生命周期。

    go 复制代码
    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.Mutexsync.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 有几种模式?

  • LockUnlock 方法用于加锁和解锁。
  • 可以使用 RLockRUnlock 实现读写锁。

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 的数量。
go 复制代码
type WorkerPool struct {
    jobs    chan Job
    wg      sync.WaitGroup
}

func (wp *WorkerPool) Start(n int) {
    for i := 0; i < n; i++ {
        go func() {
            for job := range wp.jobs {
                job.Execute()
                wp.wg.Done()
            }
        }()
    }
}

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.Mallocruntime.Free 管理堆内存,局部变量在栈上分配。

4. 大对象小对象

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

5. 堆和栈的区别

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

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

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

微服务框架

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

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

2. 怎么做服务发现的

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

其他

1. go 实现单例的方式

  • 使用 sync.Once 确保单例的安全创建。
go 复制代码
var instance *Singleton
var once sync.Once

func GetInstance() *Singleton {
    once.Do(func() {
        instance = &Singleton{}
    })
    return instance
}

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

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

3. client 如何实现长连接?

  • 使用 net 包建立 TCP 连接,并保持连接活跃。
go 复制代码
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 次。

go 复制代码
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 < 100; i++ {
            chCat <- struct{}{}
            fmt.Println("cat")
            chDog <- struct{}{}
        }
    }()

    go func() {
        defer wg.Done()
        for i := 0; i < 100; i++ {
            <-chDog
            fmt.Println("dog")
            chFish <- struct{}{}
        }
    }()

    go func() {
        defer wg.Done()
        for i := 0; i < 100; i++ {
            <-chFish
            fmt.Println("fish")
        }
    }()

    chCat <- struct{}{}
    wg.Wait()
}

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

  • 使用带缓冲的 channel 来限制 goroutine 的数量。
go 复制代码
type Job struct {
    // Job details
}

type WorkerPool struct {
    jobs    chan Job
    wg      sync.WaitGroup
}

func (wp *WorkerPool) Start(n int) {
    wp.jobs = make(chan Job, n)
    for i := 0; i < n; i++ {
        go func() {
            for job := range wp.jobs {
                // Process the job
                wp.wg.Done()
            }
        }()
    }
}

func (wp *WorkerPool) AddJob(job Job) {
    wp.wg.Add(1)
    wp.jobs <- job
}

func (wp *WorkerPool) Close() {
    close(wp.jobs)
    wp.wg.Wait()
}
本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
程序员勋勋111 分钟前
【GoLang】etcd初始化客户端时不会返回错误怎么办
后端·golang·etcd
愚润求学26 分钟前
Linux开发工具——gcc
linux·服务器·开发语言
yong158585534329 分钟前
C++ 获取一整行(一行)字符串并转换为数字
开发语言·c++
步行cgn38 分钟前
Java Properties 类详解
java·开发语言
东方苾梦41 分钟前
Elixir语言的游戏音效
开发语言·后端·golang
vvilkim1 小时前
Python四大核心数据结构深度解析:列表、元组、字典与集合
开发语言·python
Diligent_lvan1 小时前
通俗地讲述DDD的设计
java·开发语言·ddd设计
sxlzs_1 小时前
Java 策略模式(二)-实战
java·开发语言·策略模式
郭涤生1 小时前
Chapter 6: Concurrency in C++20_《C++20Get the details》_notes
开发语言·c++·笔记·c++20
倒霉蛋小马1 小时前
【Java集合】ArrayList源码深度分析
java·开发语言