面试题答案
一键面试问题原因分析
- Context泄漏:
- 在Go中,
context
通常用于控制goroutine的生命周期。如果一个goroutine没有正确处理context
的取消信号,例如在context
被取消后,该goroutine仍在执行长时间运行的任务而不检查context.Done()
通道,就会导致context
泄漏。 - 当使用
context.WithTimeout
或context.WithDeadline
创建context
时,如果在超时或截止时间到达后,相关的goroutine没有及时退出,也会造成泄漏。
- 在Go中,
- 资源竞争:
- 在高并发环境下,多个goroutine可能同时访问和修改共享资源。例如,多个定时任务可能共享一个数据库连接池、一个内存缓存等资源。
- Go的
runtime
调度器会在多个goroutine之间切换执行,如果没有适当的同步机制,就会发生资源竞争。例如,一个goroutine正在读取共享资源时,另一个goroutine可能同时对其进行修改,导致数据不一致。
优化策略与解决方案
- 针对Context泄漏:
- 主动检查取消信号:在每个使用
context
的goroutine中,定期检查context.Done()
通道。例如:
- 主动检查取消信号:在每个使用
func myTask(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
default:
// 执行任务逻辑
}
}
}
- 确保所有子goroutine正确处理取消:如果一个goroutine启动了其他子goroutine,要确保子goroutine也能收到取消信号。可以将父
context
传递给子goroutine,例如:
func parentTask(ctx context.Context) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
go childTask(ctx)
// 父goroutine逻辑
}
func childTask(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
default:
// 子goroutine任务逻辑
}
}
}
- 针对资源竞争:
- 使用互斥锁(Mutex):对于共享资源的访问,使用
sync.Mutex
进行同步。例如,假设有一个共享的计数器:
- 使用互斥锁(Mutex):对于共享资源的访问,使用
var counter int
var mu sync.Mutex
func increment() {
mu.Lock()
counter++
mu.Unlock()
}
- 读写锁(RWMutex):如果共享资源的读操作远多于写操作,可以使用
sync.RWMutex
。读操作时使用RLock
,写操作时使用Lock
。例如:
var data map[string]interface{}
var rwmu sync.RWMutex
func readData(key string) interface{} {
rwmu.RLock()
value := data[key]
rwmu.RUnlock()
return value
}
func writeData(key string, val interface{}) {
rwmu.Lock()
data[key] = val
rwmu.Unlock()
}
- 使用通道(Channel):通过通道来传递数据,避免共享资源直接在多个goroutine间访问。例如:
func producer(ch chan<- int) {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
}
func consumer(ch <-chan int) {
for val := range ch {
// 处理数据
}
}
代码层面预防和解决问题的示例
- 预防Context泄漏示例:
package main
import (
"context"
"fmt"
"time"
)
func task(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("Task cancelled")
return
default:
fmt.Println("Task is running")
time.Sleep(1 * time.Second)
}
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
go task(ctx)
time.Sleep(5 * time.Second)
}
在这个示例中,task
函数会定期检查context.Done()
通道,当context
被取消(这里是3秒超时),task
函数会退出,避免了context
泄漏。
2. 预防资源竞争示例:
package main
import (
"fmt"
"sync"
)
var counter int
var mu sync.Mutex
func increment(wg *sync.WaitGroup) {
defer wg.Done()
mu.Lock()
counter++
mu.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go increment(&wg)
}
wg.Wait()
fmt.Println("Final counter value:", counter)
}
在这个示例中,多个goroutine通过sync.Mutex
来同步对counter
的访问,避免了资源竞争,确保counter
的值是正确累加的。