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
- https://tour.golang.org/concurrency/9
- https://gobyexample.com/mutexes
- https://golang.org/pkg/sync/#Mutex.Lock
- https://segmentfault.com/a/1190000000506960?utm_source=sf-similar-article
- https://segmentfault.com/a/1190000023874384
- https://colobu.com/2018/12/18/dive-into-sync-mutex/