再过5分钟你就能了解互斥锁Mutex和共享锁RWMutex啦

573 阅读3分钟

一、互斥锁 Mutex

互斥锁也叫排他锁,同一时刻一段代码只能被一个线程运行,使用只需关注Lock(加锁)和Unlock(解锁)即可。 在Lock()和Unlock()之间的代码段称为资源的临界区(critical section),是线程安全的,任何一个时间点都只能有一个goroutine执行这段区间的代码。

1.1 没加锁,高并发结果错误

package main

import (
    "fmt"
    "sync"
)

func main() {
    var count = 0
    var wg sync.WaitGroup
    n := 10
    wg.Add(n)
    for i := 0; i < n; i++ {
        go func() {
            defer wg.Done()
            for j := 0; j < 10000; j++ {
                count++
            }
        }()
    }
    wg.Wait()
    fmt.Println(count)
}

1.1 加锁,高并发结果正确

package main

import (
	"fmt"
	"sync"
)

func main() {
	var count = 0
	var wg sync.WaitGroup
	var mu sync.Mutex
	n := 10
	wg.Add(n)
	for i := 0; i < n; i++ {
		go func() {
			defer wg.Done()
			for j := 0; j < 10000; j++ {
				mu.Lock()
				count++
				mu.Unlock()
			}
		}()
	}
	wg.Wait()
	fmt.Println(count)
}

二、共享锁、读写锁 RWMutex

互斥锁在并发量过大的情况下会导致锁等待,影响性能。

如果性能要求高,可以考虑使用更细粒度的读写锁RWMutex替换互斥锁Mutex

Go 的 sync.RWMutex 提供了两种锁:

1. 写锁 (Lock) = 互斥锁

特点:

  • ✅ 完全互斥:同时只能有一个 goroutine 持有
  • ✅ 可读可写:持有写锁的 goroutine 可以读取和修改数据
  • ❌ 阻塞所有:其他 goroutine 的读写操作都会被阻塞

2. 读锁 (RLock) = 共享锁

特点:

  • ✅ 共享读取:多个 goroutine 可以同时持有读锁
  • ❌ 只能读取:不能修改数据
  • ❌ 阻塞写入:有读锁时,写锁会被阻塞

如果某个读操作的协程加了锁,其他的协程没必要等,可以继续并发访问共享变量,让读操作并行,提高读性能。 读写锁就是一个细粒度的锁,能够在某一时刻被多个读操作持有,或者被一个写操作持有。

  1. 读写锁的读锁可以重入,在已经有读锁的情况下可以任意加读锁。
  2. 在读锁没有全部解锁的情况下,写操作会阻塞直到所有的读锁解锁
  3. 写锁定的情况下,其他协程的读写都会阻塞,直到写锁解锁

2.1 读锁:RLock/RUnlock,针对读操作

读锁没有限制,你加读锁读我也可以加读锁读,大家可以一起读,不会等到一个读操作结束后再开始下一个

package main

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

func read(m *sync.RWMutex, i int) {
    fmt.Println(i, "读操作开始")
    m.RLock()
    fmt.Println(i, "reading")
    time.Sleep(1 * time.Second)
    m.RUnlock()
    fmt.Println(i, "读操作结束")
}

func main() {
    var m sync.RWMutex
    go read(&m, 1)
    go read(&m, 2)
    go read(&m, 3)
    time.Sleep(2 * time.Second)

}

2.2 写锁:Lock/Unlock,针对写操作

写锁之间互斥,不能同时写,这就跟互斥锁一样了,一个写锁加上了,别人啥也不能干了。 写锁Lock加锁后会一直阻塞,直到Unlock释放锁,在没释放之前,不光同类的写锁会阻塞,其他类的读锁也会一直阻塞,直到能获取锁

package main

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

var count = 0

func main() {
    var m sync.RWMutex
    for i := 1; i <= 3; i++ {
        go write(&m, i)
    }
    for i := 1; i <= 3; i++ {
        go read(&m, i)
    }
    time.Sleep(1 * time.Second)
    fmt.Println("最终 count:", count)
}

func read(m *sync.RWMutex, i int) {
    fmt.Println(i, "开始读")
    m.RLock()
    fmt.Println(i, "读计数count:", count)
    time.Sleep(1 * time.Millisecond)
    m.RUnlock()
    fmt.Println(i, "读计数完毕")
}

func write(m *sync.RWMutex, i int) {
    fmt.Println(i, "开始写")
    m.Lock()
    count++
    fmt.Println(i, "写计数 count", count)
    time.Sleep(1 * time.Microsecond)
    m.Unlock()
    fmt.Println(i, "写完成")
}

三、死锁

3.1 没有解锁

Lock加锁就要Unlock解锁,RLock加锁就要RUnlock解锁,不能用错或者忘记解锁。

3.2 复制锁

加锁后传递锁一定要传指针避免造成死锁

package main

import (
    "fmt"
    "sync"
)

func main() {
    var mu sync.Mutex
    mu.Lock()
    defer mu.Unlock()
    copyTest(mu)
}

func copyTest(mu sync.Mutex) {
    mu.Lock()
    defer mu.Unlock()
    fmt.Println("ok")
}

3.3 循环等待

package main

import (
    "sync"
)

func main() {
    var muA, muB sync.Mutex
    var wg sync.WaitGroup

    wg.Add(2)
    go func() {
        defer wg.Done()
        muA.Lock()
        defer muA.Unlock()
        muB.Lock() // 等待另一个协程释放锁才能加锁
        defer muB.Lock()
    }()

    go func() {
        defer wg.Done()
        muB.Lock()
        defer muB.Lock()
        muA.Lock()  // 等待另一个协程释放锁才能加锁
        defer muA.Unlock()
    }()
    wg.Wait()
}