golang语言之互斥锁详解

互斥锁是传统的并发程序对共享资源进行访问控制的主要手段,在Go中,似乎更推崇由channel来实现资源共享和通信。它由标准库代码包sync中的Mutex结构体类型代表。只有两个公开方法:调用Lock()获得锁,调用unlock()释放锁。

  • 使用Lock()加锁后,不能再继续对其加锁(同一个goroutine中,即:同步调用),否则会panic。只有在unlock()之后才能再次Lock()。异步调用Lock(),是正当的锁竞争,当然不会有panic了。适用于读写不确定场景,即读写次数没有明显的区别,并且只允许只有一个读或者写的场景,所以该锁也叫做全局锁。

  • func (m *Mutex) Unlock()用于解锁m,如果在使用Unlock()前未加锁,就会引起一个运行错误。已经锁定的Mutex并不与特定的goroutine相关联,这样可以利用一个goroutine对其加锁,再利用其他goroutine对其解锁。

建议:同一个互斥锁的成对锁定和解锁操作放在同一层次的代码块中。 使用锁的经典模式:

var lck sync.Mutex
func foo() {
    lck.Lock() 
    defer lck.Unlock()
    // ...
}

lck.Lock()会阻塞直到获取锁,然后利用defer语句在函数返回时自动释放锁。

 

举个栗子, 验证下互斥锁堵塞情况:

package main

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

func main() {
   wg := sync.WaitGroup{}

   var mutex sync.Mutex
   fmt.Println("G0抢占中...")
   mutex.Lock()
   fmt.Println("G0已抢占.")
   wg.Add(3)

   for i := 1; i < 4; i++ {
      go func(i int) {
         fmt.Printf("G%d抢占中...\n", i)
         mutex.Lock()
         fmt.Printf("G%d已抢占.\n", i)

         time.Sleep(time.Second * 2)
         mutex.Unlock()
         fmt.Printf("G%d已释放.\n", i)
         wg.Done()
      }(i)
   }

   time.Sleep(time.Second * 5)
   fmt.Println("G0准备释放.")
   mutex.Unlock()
   fmt.Println("G0已释放.")

   wg.Wait()
}

 

输出:

G0抢占中...
G0已抢占.
G1抢占中...
G2抢占中...
G3抢占中...
G0准备释放.
G0已释放.
G1已抢占.
G1已释放.
G2已抢占.
G2已释放.
G3已抢占.
G3已释放.
 

可以清楚的看到, 只有等互斥锁被释放了, 才能被抢占.

你可能感兴趣的:(go,golang面试)