编程中级后端Go开发人员

在Go中通过通道传递值时会发生什么:复制是如何实现的,哪些类型按值传递,如何在传递复杂结构或指针时意外地获得竞争条件?

用 Hintsage AI 助手通过面试

回答

在Go中,可以通过通道发送任何类型的值:int、struct、指针、接口等。

  • 按值传递:标准类型和结构(不包括指针)被复制,接收方获得自己的副本,修改不会影响原始值。
  • 竞争检查:如果通过通道传递指针,发送方和接收方操作的是同一内存区域——可能发生数据竞争!
  • 具有嵌套指针的复杂结构:即使主要结构按值传递,嵌套的指针作为引用复制,嵌套对象级别上的竞争是可能的。

代码示例:

type Data struct { N int } c := make(chan Data) d := Data{N: 1} c <- d // 整个结构体被复制 p := &Data{N: 3} c2 := make(chan *Data) c2 <- p // 通道发送的是指向同一对象的指针

反向提问

如果通过通道传递一个包含指针字段的结构体——在通道两端修改该字段会产生竞争条件吗?

回答:

  • 是的,可能会有竞争!结构体被复制,但嵌套指针仍然引用同一内存区域。如果双方都通过引用修改数据,将会产生竞争条件。

示例:

type Box struct { Ptr *int } x := 10 chanBox := make(chan Box) chanBox <- Box{Ptr: &x} // 发送方和接收方都可以访问x!

由于对该主题细节的不了解而引发的实际错误示例


故事

在分布式队列中,经常出现“随机”崩溃和奇怪的任务结构值。结果发现,通过通道传递了指向共享结构的指针,这些结构在多个goroutine中并行修改。决定通过传递数据副本来解决。


故事

异步消息处理在内部使用了指向共享数组的切片指针,导致通过通道并行传递的同一内存部分从不同位置被修改,造成隐蔽的bug和数据损坏。


故事

在推送通知服务中,通过通道传递指向对象的引用,之后被goroutine处理。在同时结束工作和关闭通道时,一些结构仍在修改,导致崩溃或数据竞争。通过在发送前复制结构来解决。