面试题答案
一键面试设计方案
可以使用 context.Context
来实现优雅地解除阻塞。以下是一个示例代码:
package main
import (
"context"
"fmt"
"time"
)
func sendData(ctx context.Context, ch chan<- int) {
for i := 0; ; i++ {
select {
case <-ctx.Done():
return
case ch <- i:
fmt.Printf("Sent: %d\n", i)
}
}
}
func main() {
ch := make(chan int, 10)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
go sendData(ctx, ch)
for {
select {
case <-ctx.Done():
close(ch)
return
case data, ok := <-ch:
if!ok {
return
}
fmt.Printf("Received: %d\n", data)
}
}
}
在上述代码中:
context.WithTimeout
创建了一个带有超时的context.Context
。- 在发送端的
sendData
函数中,使用select
语句监听ctx.Done()
信号,当接收到该信号时,退出发送循环。 - 在接收端的
main
函数中,同样使用select
语句监听ctx.Done()
信号,当接收到该信号时,关闭通道并退出接收循环。
并发场景下的注意事项
- 资源释放:在
context.Context
取消时,要确保所有相关的资源(如打开的文件、网络连接等)都能被正确释放。 - 取消传播:如果在一个 goroutine 中调用了其他可能阻塞的函数,要将
context.Context
传递下去,确保这些函数也能响应取消信号。 - 避免死锁:在使用
select
语句时,要确保所有可能的情况都被考虑到,避免出现死锁。例如,如果ch
通道已满且没有其他情况可以处理,发送操作会阻塞,此时需要有一个退出机制(如ctx.Done()
)。 - 通道关闭:要确保通道只被关闭一次,否则会导致
panic
。在上述代码中,通过在接收端监听ctx.Done()
信号并关闭通道来避免多次关闭。 - 性能考虑:过多地使用
context.Context
可能会带来一定的性能开销,因此要根据实际情况权衡使用。在一些简单场景下,如果不需要复杂的取消逻辑,可能可以使用更轻量级的方式(如使用一个简单的标志变量)。