导航菜单

Golang 面试题精讲

本章节精选 Golang 高频面试题,每道题都包含详细的解析和扩展知识。

题目 1:实现单例模式

🟡 中等

题目描述

使用 sync.Once 实现一个线程安全的单例模式。

参考答案

参考答案 (2 个标签)
sync.Once 单例模式
package singleton

import "sync"

type Singleton struct {
    value int
}

var (
    instance *Singleton
    once     sync.Once
)

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

关键点

  1. sync.Once 保证:函数只执行一次
  2. 线程安全:多个 goroutine 同时调用,只有一个执行初始化
  3. 延迟初始化:首次调用时才创建实例

扩展:为什么不用双重检查锁?

// ❌ Go 不需要双重检查锁
var mu sync.Mutex

func GetInstance() *Singleton {
    if instance == nil {  // 第一次检查
        mu.Lock()
        defer mu.Unlock()
        if instance == nil {  // 第二次检查
            instance = &Singleton{}
        }
    }
    return instance
}

Go 的 sync.Once 已经优化了性能,双重检查锁没有优势。


题目 2:实现 Limit 并发控制

🔴 困难

题目描述

实现一个 Limit 函数,限制最多 n 个 goroutine 并发执行。

参考答案

参考答案 (2 个标签)
channel 并发控制
func Limit(n int, tasks []func()) {
    sem := make(chan struct{}, n)
    var wg sync.WaitGroup
    
    for _, task := range tasks {
        wg.Add(1)
        sem <- struct{}{} // 获取信号量
        
        go func(f func()) {
            defer wg.Done()
            defer func() { <-sem }() // 释放信号量
            f()
        }(task)
    }
    
    wg.Wait()
}

使用示例

tasks := []func(){
    func() { fmt.Println("task 1") },
    func() { fmt.Println("task 2") },
    func() { fmt.Println("task 3") },
}

Limit(2, tasks) // 最多 2 个并发

关键点

  1. 缓冲 channel 作为信号量:容量为 n
  2. 发送操作阻塞:超过 n 个 goroutine 时阻塞
  3. defer 释放:确保信号量一定被释放

题目 3:实现 Merge Sorted Array

🟢 简单

题目描述

合并两个已排序的数组。

参考答案

参考答案 (2 个标签)
双指针 数组
func MergeSorted(a, b []int) []int {
    result := make([]int, 0, len(a)+len(b))
    i, j := 0, 0
    
    for i < len(a) && j < len(b) {
        if a[i] <= b[j] {
            result = append(result, a[i])
            i++
        } else {
            result = append(result, b[j])
            j++
        }
    }
    
    // 追加剩余元素
    result = append(result, a[i:]...)
    result = append(result, b[j:]...)
    
    return result
}

复杂度分析

  • 时间复杂度:O(m + n)
  • 空间复杂度:O(m + n)

题目 4:实现 LRU 缓存

🔴 困难

题目描述

实现一个线程安全的 LRU(最近最少使用)缓存。

参考答案

参考答案 (3 个标签)
LRU 双向链表 Map
type LRUCache struct {
    capacity int
    cache    map[int]*Node
    head     *Node
    tail     *Node
    mu       sync.Mutex
}

type Node struct {
    key   int
    value int
    prev  *Node
    next  *Node
}

func NewLRUCache(capacity int) *LRUCache {
    head := &Node{}
    tail := &Node{}
    head.next = tail
    tail.prev = head
    
    return &LRUCache{
        capacity: capacity,
        cache:    make(map[int]*Node),
        head:     head,
        tail:     tail,
    }
}

func (lru *LRUCache) Get(key int) int {
    lru.mu.Lock()
    defer lru.mu.Unlock()
    
    if node, ok := lru.cache[key]; ok {
        lru.moveToHead(node)
        return node.value
    }
    return -1
}

func (lru *LRUCache) Put(key, value int) {
    lru.mu.Lock()
    defer lru.mu.Unlock()
    
    if node, ok := lru.cache[key]; ok {
        node.value = value
        lru.moveToHead(node)
    } else {
        node := &Node{key: key, value: value}
        lru.cache[key] = node
        lru.addToHead(node)
        
        if len(lru.cache) > lru.capacity {
            lru.removeTail()
        }
    }
}

func (lru *LRUCache) moveToHead(node *Node) {
    lru.removeNode(node)
    lru.addToHead(node)
}

func (lru *LRUCache) addToHead(node *Node) {
    node.prev = lru.head
    node.next = lru.head.next
    lru.head.next.prev = node
    lru.head.next = node
}

func (lru *LRUCache) removeNode(node *Node) {
    node.prev.next = node.next
    node.next.prev = node.prev
}

func (lru *LRUCache) removeTail() {
    node := lru.tail.prev
    lru.removeNode(node)
    delete(lru.cache, node.key)
}

关键点

  1. 双向链表:维护访问顺序,O(1) 移动节点
  2. 哈希表:O(1) 查找节点
  3. 互斥锁:保证并发安全

复杂度分析

  • Get:O(1)
  • Put:O(1)
  • 空间:O(capacity)

题目 5:实现生产者-消费者

🟡 中等

题目描述

使用 channel 实现一个生产者-消费者模型,支持多个生产者和消费者。

参考答案

参考答案 (2 个标签)
channel 生产者消费者
func ProducerConsumer(producers, consumers, items int) {
    jobs := make(chan int, items)
    results := make(chan int, items)
    
    // 启动生产者
    for i := 0; i < producers; i++ {
        go func(id int) {
            for j := 0; j < items/producers; j++ {
                job := id*100 + j
                jobs <- job
                fmt.Printf("Producer %d: produced %d\n", id, job)
            }
        }(i)
    }
    
    // 启动消费者
    for i := 0; i < consumers; i++ {
        go func(id int) {
            for job := range jobs {
                result := job * 2
                results <- result
                fmt.Printf("Consumer %d: consumed %d, result %d\n", id, job, result)
            }
        }(i)
    }
    
    // 等待所有生产者完成
    time.Sleep(time.Second)
    close(jobs)
    
    // 收集结果
    for i := 0; i < items; i++ {
        <-results
    }
}

关键点

  1. 缓冲 channel:解耦生产者和消费者
  2. 关闭 channel:通知消费者结束
  3. range 遍历:自动检测 channel 关闭

题目 6:实现 Context 超时控制

🟡 中等

题目描述

实现一个带超时的 HTTP 请求函数。

参考答案

参考答案 (3 个标签)
context HTTP 超时
func FetchWithTimeout(url string, timeout time.Duration) (string, error) {
    ctx, cancel := context.WithTimeout(context.Background(), timeout)
    defer cancel()
    
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return "", err
    }
    
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()
    
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return "", err
    }
    
    return string(body), nil
}

使用示例

result, err := FetchWithTimeout("https://example.com", 5*time.Second)
if err != nil {
    log.Printf("error: %v", err)
    return
}
fmt.Println(result)

关键点

  1. context.WithTimeout:创建超时上下文
  2. defer cancel():确保资源释放
  3. http.NewRequestWithContext:绑定上下文

题目 7:实现 Worker Pool

🔴 困难

题目描述

实现一个 Worker Pool,支持动态调整 worker 数量。

参考答案

参考答案 (2 个标签)
Worker Pool 并发
type WorkerPool struct {
    tasks    chan Task
    workers  int
    wg       sync.WaitGroup
    quit     chan struct{}
    mu       sync.RWMutex
}

type Task func()

func NewWorkerPool(workers int) *WorkerPool {
    return &WorkerPool{
        tasks:   make(chan Task, 100),
        workers: workers,
        quit:    make(chan struct{}),
    }
}

func (wp *WorkerPool) Start() {
    wp.mu.Lock()
    defer wp.mu.Unlock()
    
    for i := 0; i < wp.workers; i++ {
        wp.wg.Add(1)
        go wp.worker()
    }
}

func (wp *WorkerPool) worker() {
    defer wp.wg.Done()
    
    for {
        select {
        case task, ok := <-wp.tasks:
            if !ok {
                return
            }
            task()
        case <-wp.quit:
            return
        }
    }
}

func (wp *WorkerPool) Submit(task Task) {
    wp.tasks <- task
}

func (wp *WorkerPool) Stop() {
    close(wp.tasks)
    wp.wg.Wait()
}

func (wp *WorkerPool) Resize(n int) {
    wp.mu.Lock()
    defer wp.mu.Unlock()
    
    if n > wp.workers {
        // 增加 workers
        for i := 0; i < n-wp.workers; i++ {
            wp.wg.Add(1)
            go wp.worker()
        }
    } else if n < wp.workers {
        // 减少 workers
        for i := 0; i < wp.workers-n; i++ {
            wp.quit <- struct{}{}
        }
    }
    wp.workers = n
}

关键点

  1. 任务队列:缓冲 channel
  2. 动态调整:通过 quit channel 通知 worker 退出
  3. 优雅停止:关闭任务队列,等待所有 worker 完成

题目 8:实现 Rate Limiter

🟡 中等

题目描述

实现一个令牌桶限流器。

参考答案

参考答案 (2 个标签)
限流 令牌桶
type RateLimiter struct {
    rate     int           // 令牌生成速率(每秒)
    capacity int           // 桶容量
    tokens   int           // 当前令牌数
    lastTime time.Time     // 上次取令牌时间
    mu       sync.Mutex
}

func NewRateLimiter(rate, capacity int) *RateLimiter {
    return &RateLimiter{
        rate:     rate,
        capacity: capacity,
        tokens:   capacity,
        lastTime: time.Now(),
    }
}

func (rl *RateLimiter) Allow() bool {
    rl.mu.Lock()
    defer rl.mu.Unlock()
    
    now := time.Now()
    elapsed := now.Sub(rl.lastTime).Seconds()
    
    // 计算新增令牌
    newTokens := int(elapsed * float64(rl.rate))
    rl.tokens = min(rl.tokens+newTokens, rl.capacity)
    rl.lastTime = now
    
    if rl.tokens > 0 {
        rl.tokens--
        return true
    }
    return false
}

func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}

使用示例

limiter := NewRateLimiter(10, 100) // 每秒 10 个令牌,桶容量 100

for i := 0; i < 20; i++ {
    if limiter.Allow() {
        fmt.Println("Request allowed")
    } else {
        fmt.Println("Request denied")
    }
    time.Sleep(100 * time.Millisecond)
}

关键点

  1. 令牌生成:根据时间差计算
  2. 桶容量限制:不超过容量
  3. 线程安全:使用互斥锁

题目 9:实现两数之和

🟢 简单

题目描述

给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。

参考答案

参考答案 (2 个标签)
哈希表 数组
func TwoSum(nums []int, target int) []int {
    m := make(map[int]int)
    
    for i, num := range nums {
        complement := target - num
        if j, ok := m[complement]; ok {
            return []int{j, i}
        }
        m[num] = i
    }
    
    return nil
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

题目 10:实现反转链表

🟢 简单

题目描述

反转一个单链表。

参考答案

参考答案 (2 个标签)
链表 迭代
type ListNode struct {
    Val  int
    Next *ListNode
}

func ReverseList(head *ListNode) *ListNode {
    var prev *ListNode
    curr := head
    
    for curr != nil {
        next := curr.Next
        curr.Next = prev
        prev = curr
        curr = next
    }
    
    return prev
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

总结

这些面试题涵盖了 Golang 的核心知识点:

题目考点难度
单例模式sync.Once
并发控制channel
合并数组双指针
LRU 缓存链表 + Map
生产者消费者channel
超时控制context
Worker Pool并发
限流器算法
两数之和哈希表
反转链表链表

面试准备建议

  1. 手写代码:熟练掌握这些题目的代码实现
  2. 理解原理:不仅要知道怎么写,还要知道为什么
  3. 扩展思考:考虑边界条件、并发安全、性能优化
  4. 实践项目:在实际项目中应用这些模式

搜索