AI智能
改变未来

Golang从入门到放弃200711–Lock

  • 参考文章
    Go语言并发之道
    Go并发编程

  • 互斥锁、读写锁
    临界区: 程序中需要独占访问共享资源的区域

    互斥锁

      锁住临界区
    1. 由 sync.Mutex结构体类型表示
    2. 只有两个公开的
      指针方法

      : Lock() 和 Unlock()

    3. sync.Mutex类型的零值表示未被锁定的互斥量
    4. 如果锁定了一个已锁定的互斥锁,重复锁定操作的goroutine将被阻塞
    5. 如果给一个未锁定的互斥锁进行解锁操作时,就会panic,不可恢复
    6. 首次使用后,互斥锁不能复制
    7. 锁可以被多个协程共享,但建议同一个互斥锁的加锁解锁在同一层次
  • 读写锁

      锁住临界区
    1. 由 sync.RWMutex结构体类型表示
    2. 有两对方法:
      Lock() 和 Unlock() 写操作的锁定和解锁
      RLock() 和 RUnlock() 读操作的锁定和解锁
    3. sync.Mutex类型的零值表示未被锁定的互斥量
    4. 与互斥锁的不同:分别对读操作和写操作进行锁定和解锁操作
    5. 没有锁定的读写两类锁,进行对应的解锁时,都会被panic,不可恢复
    6. 首次使用后,读写锁不能复制
    7. 写独占,读共享,写锁优先级高
  • Demo示例加深印象

      互斥锁

        如果锁定一个已经锁定的互斥锁,重复锁定操作的goroutine将会被阻塞

        var lock sync.Mutexfmt.Println(\"Lock the lock.(main)\")lock.Lock()fmt.Println(\"The lock is locked.(main)\")for i := 1; i <= 3; i++ {go func(i int) {fmt.Printf(\"Lock the lock.(g%d)\\n\", i)lock.Lock()//defer lock.Unlock()  加不加之后结果比较fmt.Printf(\"The lock is locked.(g%d)\\n\", i)}(i)}time.Sleep(time.Second)fmt.Println(\"Unlock the lock.(main)\")lock.Unlock()fmt.Println(\"The lock is unlocked.(main)\")time.Sleep(time.Second)

      运行之后的结果:

      Lock the lock.(main) 	The lock is locked.(main) 	Lock the lock.(g3) 	Lock the lock.(g2) 	Lock the lock.(g1) 	Unlock the lock.(main) 	The lock is unlocked.(main) 	The lock is locked.(g3)

      从结果上可以看出:
      3个goroutine上的都已经提示已经锁住,当未解锁时,只有打印出一个goroutine的结果。如果把注释处的

      defer lock.Unlock()

      加入代码,就会发现3个
      goroutine都会打印出来。

      1. 如果给一个未锁定的互斥锁进行解锁操作时,就会panic,不可恢复

        defer func() {fmt.Println(\"Try to recover the panic.\")if  p := recover(); p != nil{fmt.Printf(\"Recovered the panic(%#v).\\n\",p)}}()var mutex sync.Mutexmutex.Lock()mutex.Unlock()fmt.Println(\"Unlock the lock.\")mutex.Unlock()

        运行结果:

        Unlock the lock again.fatal error: sync: unlock of unlocked mutex

        可以看到,就算试图去恢复panic,程序依旧终止了

      2. 首次使用后,互斥锁不能复制

        var lock sync.Mutexfor i := 0; i < 5; i++ {go func() {lock.Lock()j++fmt.Printf(\"j = %d\\t\", j)lock.Unlock()}()}time.Sleep(1 * time.Second)

        运行的结果:

        j = 1	j = 2	j = 3	j = 4	j = 5

        多次运行之后仍然可以发现,j的结果是顺序打印的。

        更改下代码,让sync.Mutex值传入到goroutine

        var lock sync.Mutexfor i := 0; i < 5; i++ {go func(lock sync.Mutex) {lock.Lock()j++fmt.Printf(\"j = %d\\t\", j)lock.Unlock()}(lock)}time.Sleep(1 * time.Second)

        多次运行之后会发现,值的顺序是不定的。

        这里再埋个坑:无论运行多少次都会发现没有出现抢占的异常

    • 读写锁

        有两对方法:
      1. 写独占,读共享,写锁优先级高
      var rwm sync.RWMutexfor i := 0; i < 3; i++ {go func(i int) {fmt.Printf(\"Try to lock for reading...[%d]\\n\", i)rwm.RLock()fmt.Printf(\"Locked for reading:[%d]\\n\", i)time.Sleep(2 * time.Second)fmt.Printf(\"Try to unlock for reading...[%d]\\n\", i)rwm.RUnlock()fmt.Printf(\"Unlocked for reading:[%d]\\n\", i)}(i)}time.Sleep(100 * time.Millisecond)  //fmt.Println(\"Try to lock for writing...\")rwm.Lock()fmt.Println(\"Locked for writing.\")

      运行结果:

      Try to lock for reading...[0]Locked for reading:[0]Try to lock for reading...[1]Locked for reading:[1]Try to lock for reading...[2]Locked for reading:[2]Try to lock for writing...Try to unlock for reading...[1]Unlocked for reading:[1]Try to unlock for reading...[2]Unlocked for reading:[2]Try to unlock for reading...[0]Unlocked for reading:[0]Locked for writing.

      直接看运行结果看不出来什么,我把结果分成了两段,从打印时间上有很明显的间
      隔。很明显因为goroutine中多次读锁锁住后,在

      main

      中的

      rwm.Lock()

      阻塞了
      所以,间隔了2s左右后,再次打印出结果goroutine中解锁后的结果。同时写锁成
      功锁住,程序不再阻塞,成功结束

      main

      中的

      time.Sleep(100 * time.Millisecond)

      挪到main的末尾,多次运
      行,会有两种结果。其中一种结果是:

      Try to lock for writing...Locked for writing.Try to lock for reading...[0]Try to lock for reading...[2]Try to lock for reading...[1]

      可以发现,当写锁锁定未解锁时,读锁的锁定是会阻塞的。

      写独占,读共享

      完整的意思是:

      1. 多个写操作之间是互斥的
      2. 写操作与读操作之间也是互斥的
      3. 多个读操作之间不是互斥的
  • 赞(0) 打赏
    未经允许不得转载:爱站程序员基地 » Golang从入门到放弃200711–Lock