
在 go 中,若通过值传递返回结构体实例,即使 goroutine 修改了其字段(如切片),调用方仍看到原始值——因为结构体被完整复制,修改作用于副本而非原实例。解决方法是返回结构体指针,确保所有协程操作同一内存地址。
在 go 中,若通过值传递返回结构体实例,即使 goroutine 修改了其字段(如切片),调用方仍看到原始值——因为结构体被完整复制,修改作用于副本而非原实例。解决方法是返回结构体指针,确保所有协程操作同一内存地址。
这个问题的核心在于 Go 的值语义(value semantics):当 Create() 函数返回 Container 类型(而非 *Container)时,整个结构体(包括其字段)会被复制一份并传给调用方。即使 goroutine 修改了 instance.values,它修改的是该 goroutine 所持有的那个栈上 Container 实例的字段;而 main 函数中接收到的 instance 是另一个独立副本,二者内存地址不同,互不影响。
以下为修复后的正确实现:
type Container struct {
values []string
}
func Create() *Container { // ✅ 返回指针,共享同一实例
instance := &Container{
values: []string{"initial value"},
}
go func() {
instance.values = []string{"modified value"} // 修改堆上同一对象
fmt.Print("values updated")
}()
return instance // 返回指向堆上实例的指针
}
func main() {
instance := Create() // instance 类型为 *Container
time.Sleep(100 * time.Millisecond)
fmt.Printf("%v", instance.values) // 输出: [modified value]
}⚠️ 注意事项:
- 避免冗余解引用:*&Container{...} 等价于 Container{...},既无必要又易误导,应直接使用 &Container{...} 创建指针;
- 内存分配位置:使用 &Container{...} 会将结构体分配在堆上(因需被多个 goroutine 共享),而 Container{...} 通常分配在栈上(生命周期受限于当前函数);
- 并发安全未保障:本例虽能“看到”修改,但 values 字段的读写仍存在竞态(race condition)。若需安全读写,应配合 sync.Mutex 或 sync.RWMutex 加锁,或改用通道(channel)进行通信;
- 更推荐的实践:优先采用「通过通道通信,而非共享内存」原则。例如,可让 goroutine 将新值通过 channel 发送给 main,从而避免显式共享状态。
总结:Go 中结构体默认按值传递;要实现跨 goroutine 的状态共享,必须使用指针(*T)并确保所有协程操作同一内存地址。但务必同步访问——指针解决可见性问题,而互斥锁或通道解决正确性问题。

















