从一个警告想到的关于锁的小问题

文章讲述了在Go语言中,使用golangci-lint检查代码时发现的一个关于结构体中包含sync.Mutex锁的问题。当结构体被复制时,锁也会被复制,这可能导致并发安全问题,因为不同的副本持有不同的锁,破坏了互斥访问的原意。正确的做法是传递锁的指针,确保所有协程使用同一把锁。通过一个示例程序说明了返回结构体指针和返回结构体副本两种情况的并发行为差异。
摘要由CSDN通过智能技术生成

背景:今天心血来潮用 golangci-lint 检查自己的代码,发现了一个误用kitex中结构体的警告

internal/user/dao/user.go:45:11: copylocks: return copies lock value: TTMS/kitex_gen/user.User contains 
google.golang.org/protobuf/internal/impl.MessageState contains sync.Mutex (govet)
			return u, nil

第一反应是赶紧看怎么解决,结果发现返回一个指针类型就没有警告了,后来仔细想了一下,发现确实是个之前没注意到的小问题,但关键时刻也可能念成大祸!

如果结构体中的锁被复制了(即返回的结构体是原始结构体的副本),则在使用该结构体时会出现问题。这是因为 sync.Mutex 锁是一个互斥量,它的主要作用是用于多个 goroutine 对共享资源进行互斥访问。如果一个结构体的副本中包含一个锁的副本,则在使用该结构体的时候,可能会导致两个 goroutine 同时访问同一个共享资源,从而破坏了锁的互斥性。

一句话总结就是:一个锁的复制出来的副本和原来是不一样的,但是把锁对象的指针传出去,所有能引用该指针的对象拿到的都是同一把锁。

比如,以下示例:


import (
	"fmt"
	"sync"
	"time"
)

type MyStruct struct {
	mutex sync.Mutex
	data  int
}

// 1.返回 *MyStruct 时,s1 s2 用的是同一把锁
func getMyStruct() *MyStruct {
	return &MyStruct{data: 0}
}

// 2.返回 MyStruct 时,s2 用的是 s1中锁的副本,本质上是两把锁
//
//	func getMyStruct() MyStruct {
//		return MyStruct{data: 0}
//	}
func main() {
	s1 := getMyStruct()
	s2 := s1
	go func() {
		for i := 0; i < 10000; i++ {
			s1.mutex.Lock()
			s1.data++
			s1.mutex.Unlock()
		}
	}()

	go func() {
		for i := 0; i < 10000; i++ {
			s2.mutex.Lock()
			s1.data++
			s2.mutex.Unlock()
		}
	}()

	// 等待 goroutine 结束
	time.Sleep(1 * time.Second)

	fmt.Println(s1.data)
	// 1.当两个协程使用同一把锁时 结果为 20000
	// 2.当两个协程使用不同锁时(相当直接没加锁) 结果一般不足 20000
}

 在上面的示例中,我们 s1 传给 s2,并在两个 goroutine 中对它们进行修改。

传递锁的指针和锁的副本两种情况分别对应 1,2 两种处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

倚风听雨.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值