Go sync.Mutex 为什么不能复制
来源:17golang原创
时间:2026-11-14 16:27:02 194浏览 收藏
Go 官方要求 sync.Mutex 在首次使用后不能复制。互斥关系依赖同一把锁的身份,复制结构体会得到另一份锁状态,无法继续代表原来那把锁。实际写法上,含 Mutex 的共享对象通常通过指针传递,读写方法使用指针接收者,并让同一份数据始终受同一个锁对象保护。
官方地址:https://pkg.go.dev/sync
先用指针方法绑定同一把锁
最小可用的设计是把锁和受保护字段放进一个对象,用指针方法封装访问。下面的 Counter 包含 mu 与 n;Add 和 Value 都使用同一个对象中的 mu,不把整个 Counter 返回给外部。
package main
import (
"fmt"
"sync"
)
type Counter struct {
mu sync.Mutex
n int
}
func (c *Counter) Add(delta int) {
// 指针接收者让每次调用使用同一个对象中的锁。
c.mu.Lock()
defer c.mu.Unlock()
c.n += delta
}
func (c *Counter) Value() int {
// 读取也遵守相同的锁协议,只返回不含锁的数据。
c.mu.Lock()
defer c.mu.Unlock()
return c.n
}
func main() {
c := &Counter{}
var wg sync.WaitGroup
for i := 0; i
该示例的逻辑结果应为 400:四个协程各累加一百次,等待全部结束再读取。这里给出的是代码推导,不是本地执行记录。示例中 Counter 的零值可用,不需要通过复制另一把 Mutex 来初始化锁。
解释首次使用后禁止复制
Mutex 是有状态的同步对象。不要把它理解成一份“调用 Lock 的配置”,以为复制之后两边仍能共同排队。两个不同 Mutex 对象之间没有因为字段值相似就建立互斥关系。
复制已使用的锁可能把内部状态带进新对象,造成等待无法按原有关系结束。另一方面,即使复制时锁处于未锁定状态,两份锁也不是同一把锁,不能用来共同保护同一份共享数据。问题既有锁状态生命周期,也有数据保护关系,不能仅凭“现在没有上锁”判断可复制。

官方约束精确到“首次使用后”。独立创建多个全新的零值对象当然可以,每个对象拥有自己的锁与自己的数据。但如果字段里还有 map、slice 或指针,按值复制未必意味着数据独立。工程上让这类对象从构造到使用都通过指针流转,往往比要求所有调用方判断锁是否使用过更清楚。
识别值接收者和按值传递
方法写成值接收者时,调用会复制接收者。下面是需要避免的写法,仅用于说明复制位置:
// 错误示意:值接收者复制 Counter,锁住的是副本。
func (c Counter) AddWrong(delta int) {
c.mu.Lock()
defer c.mu.Unlock()
c.n += delta
}
对于这个只含整数的例子,修改的是副本中的 n,原对象不会按预期更新。如果受保护字段换成 map,复制结构体又会保留对同一底层 map 的引用,而锁却变成不同副本;多个调用可能分别拿着自己的锁访问同一份数据。加锁语句存在,并不等于共享数据被同一把锁保护。
隐式复制也出现在按值参数、结构体赋值、把对象值装入接口或容器等位置。把方法改成指针接收者只是其中一部分,还需要保证调用链不再把整个已使用对象按值搬走。
| 位置 | 风险 | 常见处理 |
|---|---|---|
| 值接收者 | 方法得到结构体副本 | 使用指针接收者 |
| 按值函数参数 | 参数复制含锁对象 | 传入对象指针 |
| 对象赋值 | 复制锁与字段 | 共享指针或构造独立对象 |
| 业务快照 | 整对象返回会携带锁 | 只复制锁内读取的数据 |
安排容器与快照的结构
如果一组含锁对象需要放在可增长切片中,可考虑保存对象指针。对存放结构体值的切片进行扩容,会复制元素;如果元素中的锁已使用过,就不应再依赖这种搬移。遍历时把元素复制到循环变量,也会带来相同类型的问题。
使用对象指针切片时,切片复制的是指针,所指对象本身的身份保持不变。不过容器本身的并发修改仍需要单独管理,不能因为元素有锁就认为 append 或替换元素也自动安全。
快照也应只输出业务数据。上面的 Value 就返回 int。如果未来需要返回多个字段,可定义一个不含同步字段的快照结构体,在锁内读取字段,再返回快照。对于 map、slice 这样的引用型内容,是否深拷贝取决于快照的隔离要求。

把 Mutex 字段改成 Mutex 指针也不是通用修复:它可能让不同数据副本共享一把锁,还引入 nil 和初始化约束。应先明确哪些数据属于同一个共享对象,再选择对象引用方式。
用工具提示辅助定位复制
Go 工具链的 copylocks 分析关注锁被错误按值传递的情况。项目开发时可用 go vet 的相关提示定位值接收者、参数和赋值等复制位置;动态竞争检测则关注被实际执行路径中的竞争。工具没有报告不能替代对象身份与锁协议的设计。
采用这种设计时,维护者最直接的收益是能回答两个具体问题:每份共享数据归哪个对象,所有并发访问是否经过同一把锁。性能与布局优化应在这个关系明确之后再做,不需要因为一条复制问题就替换全部同步机制。
复制 Mutex 指针会复制锁吗?
复制指针仍指向同一个 Mutex,不会复制所指锁对象。但对象是否存活、数据是否仍由它保护,需要由接口约定保证。
Unlock 后就能复制整个结构体吗?
不能。官方规则是首次使用后不能复制,并不以当前是否锁定为判断条件。
每个方法都有 Lock 为什么还有问题?
先确认锁是否来自同一个对象,以及共享字段是否通过副本、map 或指针继续指向同一份数据。不同锁不会相互排斥。
-
502 收藏
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
215 收藏
-
257 收藏
-
180 收藏
-
143 收藏
-
500 收藏
-
153 收藏
-
353 收藏
-
265 收藏
-
146 收藏
-
434 收藏
-
342 收藏
-
111 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习