Golang 并发和锁

Golang 并发和锁

1.介绍

读写锁介绍

基本遵循两大原则:

1、可以随便读,多个goroutine同时读

2、写的时候,啥也不能干。不能读也不能写

RWMutex提供了四个方法:

func (*RWMutex) Lock // 写锁定

func (*RWMutex) Unlock // 写解锁

func (*RWMutex) RLock // 读锁定

func (*RWMutex) RUnlock // 读解锁

  • 读锁的时候无需等待读锁的结束
  • 读锁的时候要等待写锁的结束
  • 写锁的时候要等待读锁的结束
  • 写锁的时候要等待写锁的结束

其他介绍

golang中的锁分为互斥锁、读写锁、原子锁即原子操作。在 Golang 里有专门的方法来实现锁,就是 sync 包,这个包有两个很重要的锁类型。一个叫 Mutex, 利用它可以实现互斥锁。一个叫 RWMutex,利用它可以实现读写锁。

全局锁 sync.Mutex,是同一时刻某一资源只能上一个锁,此锁具有排他性,上锁后只能被此线程使用,直至解锁。加锁后即不能读也不能写。全局锁是互斥锁,即 sync.Mutex 是个互斥锁。

读写锁 sync.RWMutex ,将使用者分为读者和写者两个概念,支持同时多个读者一起读共享资源,但写时只能有一个,并且在写时不可以读。理论上来说,sync.RWMutex 的 Lock() 也是个互斥锁。
将上面的结论展开一下,更清晰得说(为避免理解偏差宁可唠叨一些):

sync.Mutex 的锁是不可以嵌套使用的。
sync.RWMutex 的 mu.Lock() 是不可以嵌套的。
sync.RWMutex 的 mu.Lock() 中不可以嵌套 mu.RLock()。(这是个注意的地方)
否则,会 panic fatal error: all goroutines are asleep - deadlock!

下面三种均会死锁

func lockAndRead1() { // 全局锁内使用全局锁
	l.Lock()
	defer l.Unlock()
 
	l.Lock()
	defer l.Unlock()
}
 
func lockAndRead2() { // 全局锁内使用可读锁
	l.Lock()
	defer l.Unlock() // 由于 defer 是栈式执行,所以这两个锁是嵌套结构
 
	l.RLock()
	defer l.RUnlock()
}
 
func lockAndRead3() { // 可读锁内使用全局锁
	l.RLock()
	defer l.RUnlock()
 
	l.Lock()
	defer l.Unlock()
}

var l sync.RWMutex
 
func lockAndRead() { // 可读锁内使用可读锁
	l.RLock()
	defer l.RUnlock()
 
	l.RLock()
	defer l.RUnlock()
}
 
func main() {
	lockAndRead()
	time.Sleep(5 * time.Second)
}

上面这种方式不会嵌套。


2.读写锁实例

2.1 同时读

package main

import (
    "sync"
    "time"
)

var m *sync.RWMutex

func main() {
    m = new(sync.RWMutex)
    
    // 多个同时读
    go read(1)
    go read(2)

    time.Sleep(2*time.Second)
}

func read(i int) {
    println(i,"read start")

    m.RLock()
    println(i,"reading")
    time.Sleep(1*time.Second)
    m.RUnlock()    

    println(i,"read over")
}

结果

1 read start

1 reading 

2 read start

2 reading

1 read over

2 read over

2.2 读写交替

package main

import (
    "sync"
    "time"
)

var m *sync.RWMutex

func main() {
    m = new(sync.RWMutex)
    
    // 写的时候啥也不能干
    go write(1)
    go read(2)
    go write(3)

    time.Sleep(2*time.Second)
}

func read(i int) {
    println(i,"read start")

    m.RLock()
    println(i,"reading")
    time.Sleep(1*time.Second)
    m.RUnlock()    

    println(i,"read over")
}

func write(i int) {
    println(i,"write start")

    m.Lock()
    println(i,"writing")
    time.Sleep(1*time.Second)
    m.Unlock()

    println(i,"write over")
}

结果

1 write start

1 writing

2 read start

3 write start

1 writing over

2 reading

2 read over

3 writing

3 write over

3.解决并发中goroutine未绑定变量

package main

import (
"fmt"
"time"
)
func main() {
names := []string{"niko","mike","tony"}
for _,name := range names{
go func (na string) {
fmt.Printf("%s\n",na)
}(name)
}
time.Sleep(time.Second * 2)
}

传送门1

传送门2


4.互斥锁

互斥锁有两个方法:加锁、解锁。

一个互斥锁只能同时被一个 goroutine 锁定,其它 goroutine 将阻塞直到互斥锁被解锁(重新争抢对互斥锁的锁定)。使用Lock加锁后,不能再进行加锁,只有当对其进行Unlock解锁之后,才能对其加锁。这个很好理解。

如果对一个未加锁的资源进行解锁,会引发panic异常。
可以在一个goroutine中对一个资源加锁,而在另外一个goroutine中对该资源进行解锁。
不要在持有锁的时候做 IO 操作。尽量只通过持有锁来保护 IO 操作需要的资源而不是 IO 操作本身。

func (m *Mutex) Lock()
func (m *Mutex) Unlock()

5.锁拷贝问题

type MyMutex struct {
	count int
	sync.Mutex
}
 
func main() {
	var mu MyMutex
	mu.Lock()
	var mu1 = mu
	mu.count++
	mu.Unlock()
	mu1.Lock() //已经加锁,此时再加锁会死锁
	mu1.count++
	mu1.Unlock()
	fmt.Println(mu.count, mu1.count)
}

加锁后复制变量,会将锁的状态也复制,所以 mu1 其实是已经加锁状态,再加锁会死锁

6.参考文章

文章1

文章2

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

酷酷的Herio

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

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

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

打赏作者

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

抵扣说明:

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

余额充值