【Golang】使用 - sync.Mutex

Posted by 西维蜀黍 on 2021-05-30, Last Modified on 2021-09-21

This concept is called mutual exclusion, and the conventional name for the data structure that provides it is mutex.

type Mutex

A Mutex is a mutual exclusion lock(排它锁). The zero value for a Mutex is an unlocked mutex.

A Mutex must not be copied after first use.

type Mutex struct {
    // contains filtered or unexported fields
}

func (*Mutex) Lock

func (m *Mutex) Lock()

Lock locks m. If the lock is already in use, the calling goroutine blocks until the mutex is available.

func (*Mutex) Unlock

func (m *Mutex) Unlock()

Unlock unlocks m. It is a run-time error if m is not locked on entry to Unlock.

A locked Mutex is not associated with a particular goroutine. It is allowed for one goroutine to lock a Mutex and then arrange for another goroutine to unlock it.

Example

Example 1

package main

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

// SafeCounter is safe to use concurrently.
type SafeCounter struct {
	mu sync.Mutex
	v  map[string]int
}

// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
	c.mu.Lock()
	// Lock so only one goroutine at a time can access the map c.v.
	c.v[key]++
	c.mu.Unlock()
}

// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
	c.mu.Lock()
	// Lock so only one goroutine at a time can access the map c.v.
	defer c.mu.Unlock()
	return c.v[key]
}

func main() {
	c := SafeCounter{v: make(map[string]int)}
	for i := 0; i < 1000; i++ {
		go c.Inc("somekey")
	}

	time.Sleep(time.Second)
	fmt.Println(c.Value("somekey"))
}

Example 2

package main

import (
    "fmt"
    "math/rand"
    "sync"
    "sync/atomic"
    "time"
)

func main() {
    var state = make(map[int]int)

    var mutex = &sync.Mutex{}

    var readOps uint64
    var writeOps uint64

    for r := 0; r < 100; r++ {
        go func() {
            total := 0
            for {

                key := rand.Intn(5)
                mutex.Lock()
                total += state[key]
                mutex.Unlock()
                atomic.AddUint64(&readOps, 1)

                time.Sleep(time.Millisecond)
            }
        }()
    }

    for w := 0; w < 10; w++ {
        go func() {
            for {
                key := rand.Intn(5)
                val := rand.Intn(100)
                mutex.Lock()
                state[key] = val
                mutex.Unlock()
                atomic.AddUint64(&writeOps, 1)
                time.Sleep(time.Millisecond)
            }
        }()
    }

    time.Sleep(time.Second)

    readOpsFinal := atomic.LoadUint64(&readOps)
    fmt.Println("readOps:", readOpsFinal)
    writeOpsFinal := atomic.LoadUint64(&writeOps)
    fmt.Println("writeOps:", writeOpsFinal)

    mutex.Lock()
    fmt.Println("state:", state)
    mutex.Unlock()
}
$ go run mutexes.go
readOps: 83285
writeOps: 8320
state: map[1:97 4:53 0:33 2:15 3:2]

特性

不可重入性(Non-reentrant)

与 Java 不同,Golang中的 Mutex是不可重入锁(Non-reentrant lock):

package main

import "sync"

var mu = &sync.Mutex{}

func main() {
	f1(mu)
}

func f1(m *sync.Mutex) {
	mu.Lock()
	// do something
	f2(mu)
	mu.Unlock()
}

func f2(m *sync.Mutex) {
	mu.Lock()
	// do something
	mu.Unlock()
}

Output:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_SemacquireMutex(0x10f887c, 0xc000010000, 0x1)
        /usr/local/go/src/runtime/sema.go:71 +0x47
sync.(*Mutex).lockSlow(0x10f8878)
        /usr/local/go/src/sync/mutex.go:138 +0xfc
sync.(*Mutex).Lock(...)
        /usr/local/go/src/sync/mutex.go:81
main.f2(0x10f8878)
        /Users/shiwei/SW/GoPlayground/sw.go:19 +0x6e
main.f1(0x10f8878)
        /Users/shiwei/SW/GoPlayground/sw.go:14 +0x46
main.main()
        /Users/shiwei/SW/GoPlayground/sw.go:8 +0x2d

Process finished with exit code 2

结论:

  • 在同一个 goroutine 中的 Mutex 解锁之前再次进行加锁,会导致死锁

Reference