Golang 互斥锁

1. Golang 互斥锁

1.1. 基础知识

对写操作的锁定和解锁, 简称"写锁定"和"写解锁":

func (*RWMutex) Lock() 
func (*RWMutex) Unlock()

对读操作的锁定和解锁, 简称为"读锁定"与"读解锁":

func (*RWMutex) RLock() 
func (*RWMutex) RUnlock()

看个不使用锁的示例:

func printer(str string) {
	for _, data := range str {
		fmt.Printf("%c", data)
	}
	fmt.Println()
}

func person1() {
	printer("hello")
}

func person2() {
	printer("world")
}

func main() {
	go person1()
	person2()
	time.Sleep(time.Second)
} //输出结果//worhello//ld

加上互斥锁的示例:

var mut sync.Mutex

func printer(str string) {
	mut.Lock()
	defer mut.Unlock()
	for _, data := range str {
		fmt.Printf("%c", data)
	}
	fmt.Println()
}

func person1() {
	printer("hello")
}

func person2() {
	printer("world")
}

func main() {
	go person1()
	person2()
	time.Sleep(time.Second)
} //输出结果//world//hello

1.2. 注意事项

1.2.1. 互斥锁

  • 不要重复锁定互斥锁: 对一个已经被锁定的互斥锁进行锁定, 是会立即阻塞当前的 goroutine, 这个 goroutine 所执行的流程, 会一直停滞在调用该互斥锁的 Lock 方法的那行代码上。(注意: 这种由 Go 语言运行时系统自行抛出的 panic 都属于致命错误, 都是无法被恢复的, 调用 recover 函数对它们起不到任何作用。也就是说, 一旦产生死锁, 程序必然崩溃。)
  • 不要忘记解锁互斥锁, 必要时使用 defer 语句: 因为在一个 goroutine 执行的流程中, 可能会出现诸如"锁定、解锁、再锁定、再解锁"的操作, 所以如果我们忘记了中间的解锁操作, 那就一定会造成重复锁定。
var mutex sync.Mutex

func write() {
	defer mutex.Unlock() // 通过 defer 解锁
	mutex.Lock()         // 获取临界资源, 执行具体逻辑。..
}
  • 不要对尚未锁定或者已解锁的互斥锁解锁: 这个程序会直接 panic。
var mutex sync.Mutex // 定义互斥锁变量 mutex

func main() {
	mutex.Lock()
	mutex.Unlock()
	mutex.Unlock() // fatal error: sync: unlock of unlocked mutexreturn
}
  • 不要在多个函数之间直接传递互斥锁: 互斥锁是一结构体类型, 即值类型, 把它传给一个函数、将它从函数中返回、把它赋给其他变量、让它进入某个通道都会导致它的副本的产生。因此, 原值和它的副本、以及多个副本之间都是完全独立的, 它们都是不同的互斥锁。

1.2.2. 读写锁

  • 在写锁已被锁定的情况下再试图锁定写锁, 会阻塞当前的 goroutine;
  • 在写锁已被锁定的情况下试图锁定读锁, 也会阻塞当前的 goroutine;
  • 在读锁已被锁定的情况下试图锁定写锁, 同样会阻塞当前的 goroutine;
  • 在读锁已被锁定的情况下再试图锁定读锁, 并不会阻塞当前的 goroutine;
  • 解锁"读写锁中未被锁定的写锁", 会立即引发 panic, 对于读锁也是如此。

上面写的有点啰嗦, 我用大白话总结一下: 我读数据时, 你可以去读, 因为我两的数据是一样的; 我读数据时, 你不能写, 你写了, 数据就变了, 我还读个鬼啊; 我写数据时, 你不能读, 也不能写, 我就是这么强势。下面看一个实例:


var count int
var mutex sync.RWMutex

func write(n int) {
	rand.Seed(time.Now().UnixNano())
	fmt.Printf("写 goroutine %d 正在写数据。..\n", n)
	mutex.Lock()
	num := rand.Intn(500)
	count = num
	fmt.Printf("写 goroutine %d 写数据结束, 写入新值 %d\n", n, num)
	mutex.Unlock()
}
func read(n int) {
	mutex.RLock()
	fmt.Printf("读 goroutine %d 正在读取数据。..\n", n)
	num := count
	fmt.Printf("读 goroutine %d 读取数据结束, 读到 %d\n", n, num)
	mutex.RUnlock()
}

func main() {
	for i := 0; i < 10; i++ {
		go read(i + 1)
	}

	for i := 0; i < 10; i++ {
		go write(i + 1)
	}
	time.Sleep(time.Second * 5)
}

输出结果:

读 goroutine 1 正在读取数据。..
读 goroutine 1 读取数据结束, 读到 0
读 goroutine 7 正在读取数据。..
读 goroutine 7 读取数据结束, 读到 0
读 goroutine 3 正在读取数据。..
读 goroutine 3 读取数据结束, 读到 0
读 goroutine 10 正在读取数据。..
读 goroutine 10 读取数据结束, 读到 0
读 goroutine 8 正在读取数据。..
读 goroutine 8 读取数据结束, 读到 0
读 goroutine 6 正在读取数据。..
读 goroutine 5 正在读取数据。..
读 goroutine 5 读取数据结束, 读到 0
写 goroutine 2 正在写数据。..
读 goroutine 4 正在读取数据。..
读 goroutine 4 读取数据结束, 读到 0
写 goroutine 4 正在写数据。..
写 goroutine 3 正在写数据。..
读 goroutine 2 正在读取数据。..
读 goroutine 2 读取数据结束, 读到 0
写 goroutine 9 正在写数据。..
读 goroutine 6 读取数据结束, 读到 0
写 goroutine 7 正在写数据。..
读 goroutine 9 正在读取数据。..
读 goroutine 9 读取数据结束, 读到 0
写 goroutine 6 正在写数据。..
写 goroutine 1 正在写数据。..
写 goroutine 8 正在写数据。..
写 goroutine 10 正在写数据。..
写 goroutine 5 正在写数据。..
写 goroutine 2 写数据结束, 写入新值 365
写 goroutine 4 写数据结束, 写入新值 47
写 goroutine 3 写数据结束, 写入新值 468
写 goroutine 9 写数据结束, 写入新值 155
写 goroutine 7 写数据结束, 写入新值 112
写 goroutine 6 写数据结束, 写入新值 490
写 goroutine 1 写数据结束, 写入新值 262
写 goroutine 8 写数据结束, 写入新值 325
写 goroutine 10 写数据结束, 写入新值 103
写 goroutine 5 写数据结束, 写入新值 353

可以看出前面 10 个协程可以并行读取数据, 后面 10 个协程, 就全部阻塞在了"… 正在写数据。…"过程, 等读完了, 然后 10 个协程就开始依次写。

1.3. 总结

本章的内容不多, 主要需要注意互斥锁和读写锁的几条注意事项, 读写锁其实就是更细粒度的锁划分, 为了能让程序更好并发, 上面已经讲述的非常清楚, 这里就不再啰嗦。唯一再强调的一点, 无论是互斥锁还是读写锁, 我们都不要试图去解锁未锁定的锁, 因为这样会引发不可恢复的 panic。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

云满笔记

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

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

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

打赏作者

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

抵扣说明:

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

余额充值