Golang 并发和锁

Posted Harris-H

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

以上是关于Golang 并发和锁的主要内容,如果未能解决你的问题,请参考以下文章

golang 实现并发的websocket

java并发之线程同步(synchronized和锁机制)

并发技术进程线程和锁拾遗

Java并发编程:线程和锁的使用与解析

并发技术进程线程和锁拾遗

并发技术进程线程和锁拾遗