面试题答案
一键面试结合context.Context
实现Goroutine安全取消及并发安全处理
-
context.Context
机制概述:context.Context
是Go语言中用于控制Goroutine生命周期的重要机制。它可以在Goroutine树中传递截止时间、取消信号等。context.Context
有几个重要的接口方法:Deadline()
:返回截止时间,用于控制Goroutine在某个时间点后自动取消。Done()
:返回一个通道,当context
被取消或超时时,该通道会被关闭。Err()
:返回context
被取消的原因。Value(key interface{}) interface{}
:用于在context
中传递一些附加数据。
-
并发安全处理共享资源: 为了处理多个Goroutine之间共享资源的并发安全问题,通常使用
sync.Mutex
(互斥锁)、sync.RWMutex
(读写互斥锁,适用于读多写少场景)或sync.Cond
(条件变量)等同步原语。 -
示例代码框架:
package main
import (
"context"
"fmt"
"sync"
"time"
)
// 模拟共享资源
type SharedResource struct {
data int
mu sync.Mutex
}
// 向共享资源写入数据
func (sr *SharedResource) Write(ctx context.Context, value int) {
for {
select {
case <-ctx.Done():
fmt.Println("Write operation cancelled")
return
default:
sr.mu.Lock()
sr.data = value
fmt.Printf("Write value: %d\n", value)
sr.mu.Unlock()
time.Sleep(100 * time.Millisecond)
}
}
}
// 从共享资源读取数据
func (sr *SharedResource) Read(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("Read operation cancelled")
return
default:
sr.mu.Lock()
fmt.Printf("Read value: %d\n", sr.data)
sr.mu.Unlock()
time.Sleep(100 * time.Millisecond)
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
sharedResource := &SharedResource{}
var wg sync.WaitGroup
wg.Add(2)
// 启动写操作的Goroutine
go func() {
defer wg.Done()
sharedResource.Write(ctx, 42)
}()
// 启动读操作的Goroutine
go func() {
defer wg.Done()
sharedResource.Read(ctx)
}()
// 模拟外部信号,500毫秒后取消
time.Sleep(500 * time.Millisecond)
cancel()
wg.Wait()
fmt.Println("All Goroutines stopped")
}
- 关键部分实现逻辑:
context.Context
创建与取消: 在main
函数中,使用context.WithCancel(context.Background())
创建了一个可取消的context
,并返回一个取消函数cancel
。当外部信号(这里模拟为time.Sleep(500 * time.Millisecond)
后)到来时,调用cancel()
函数,向所有基于该context
创建的子context
发送取消信号。SharedResource
中的操作:Write
和Read
方法中,通过select
语句监听ctx.Done()
通道。当该通道关闭时,意味着context
被取消,Goroutine会退出循环,从而安全地结束执行。同时,在对共享资源data
进行读写操作时,使用sync.Mutex
来保证并发安全。sync.WaitGroup
的使用:sync.WaitGroup
用于等待所有Goroutine完成。在启动每个Goroutine前调用wg.Add(1)
,在Goroutine结束时调用wg.Done()
,最后通过wg.Wait()
阻塞主线程,直到所有Goroutine完成任务。这样可以确保在程序退出前,所有Goroutine都能安全地结束,避免资源泄漏。