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
}关键点
- sync.Once 保证:函数只执行一次
- 线程安全:多个 goroutine 同时调用,只有一个执行初始化
- 延迟初始化:首次调用时才创建实例
扩展:为什么不用双重检查锁?
// ❌ 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 个并发关键点
- 缓冲 channel 作为信号量:容量为 n
- 发送操作阻塞:超过 n 个 goroutine 时阻塞
- 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)
}关键点
- 双向链表:维护访问顺序,O(1) 移动节点
- 哈希表:O(1) 查找节点
- 互斥锁:保证并发安全
复杂度分析
- 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
}
}关键点
- 缓冲 channel:解耦生产者和消费者
- 关闭 channel:通知消费者结束
- 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)关键点
- context.WithTimeout:创建超时上下文
- defer cancel():确保资源释放
- 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
}关键点
- 任务队列:缓冲 channel
- 动态调整:通过 quit channel 通知 worker 退出
- 优雅停止:关闭任务队列,等待所有 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)
}关键点
- 令牌生成:根据时间差计算
- 桶容量限制:不超过容量
- 线程安全:使用互斥锁
题目 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 | 并发 | 难 |
| 限流器 | 算法 | 中 |
| 两数之和 | 哈希表 | 易 |
| 反转链表 | 链表 | 易 |
面试准备建议
- 手写代码:熟练掌握这些题目的代码实现
- 理解原理:不仅要知道怎么写,还要知道为什么
- 扩展思考:考虑边界条件、并发安全、性能优化
- 实践项目:在实际项目中应用这些模式
