go中指针本身不处理共享,真正决定共享与安全的是数据类型及使用方式;传t才能修改原值,因函数参数恒为值传递,t副本存地址可解引用修改;切片/map/chan因含指针的header结构天然共享元素,但append可能使切片失效;字符串跨goroutine共享须用atomic.loadpointer避免data race;指针共享不等于线程安全,需手动同步;返回局部变量地址由逃逸分析保障堆分配,安全;chan *t可移交所有权。

Go 中指针本身不“处理”共享,它只是让多个函数能访问同一块内存地址——真正决定是否共享、是否安全的,是底层数据结构类型 + 你如何使用它。
为什么传 *T 才能让函数修改原值
Go 函数参数永远是值传递。传 T(比如 struct、int)时,函数拿到的是副本;传 *T 时,副本里存的是地址,解引用后就能定位并修改原始变量。
-
func update(p *Person) { p.Name = "X" }→ 原Person的Name被改 -
func update(p Person) { p.Name = "X" }→ 只改了栈上临时副本,调用方完全无感 - 基础类型(
int、string)、数组([3]int)、非引用结构体必须传*T才能回写
切片、map、chan 为什么不用指针也能“共享”
它们本质是包含指针字段的 header 结构:[]int 是 {ptr, len, cap},map[string]int 是 *hmap。传参时拷贝的是这个 header,所以对元素操作(s[0]=1、m["k"]=2)会反映到原数据上。
- 但
append(s, x)会可能分配新底层数组 → 新 header 不再指向原数组 → 原切片不变 - 所以跨 handler 更新
[]Article时,不能只传[]Article,得传*[]Article或用包级变量 -
map和chan没这个问题,增删改元素天然共享,但并发读写仍需同步(如sync.RWMutex)
跨 goroutine 共享字符串必须用 atomic.LoadPointer
string 是只读结构体(含指针+长度),无法原子读写。裸读裸写必触发 data race,且可能读到“撕裂值”(指针和长度不匹配)。
- 错误写法:
var s string; go func() { println(s) }()→go build -race直接报错 - 正确路径:用
unsafe.String+atomic.LoadPointer,写入前先new(string)分配堆内存,再atomic.StorePointer(&ptr, unsafe.Pointer(&s)) - 读取侧必须一步到位:
p := (*string)(atomic.LoadPointer(&ptr)); use(*p),不能缓存p或二次解引用
指针共享状态最容易被忽略的坑
共享地址 ≠ 共享线程安全。指针只是打开了一扇门,门后是否上锁、谁在进门、进门后怎么操作,全靠你自己控制。
- 结构体字段递增(
c.count++)不是原子操作,即使所有 goroutine 都持同一个*Counter,也得加sync.Mutex或用atomic.AddInt64 - 返回局部变量地址看似危险,但 Go 编译器会自动逃逸分析 →
return &T{}实际分配在堆上,安全 - 把指针塞进 channel(如
chan *Counter)可实现“所有权移交”,比锁更清晰表达“此刻谁负责修改”,但发送后原变量不能再用,否则竞争
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











