正常模式是指当有多个goroutine同时请求锁时,Mutex会公平地选择一个goroutine来获取锁,其他goroutine会被放入一个FIFO(先进先出)的等待队列中。当锁被释放时,等待队列中的goroutine会按照先后顺序获取锁。
饥饿模式是指当有多个goroutine同时请求锁时,Mutex不公平地选择一个goroutine来获取锁,而是优先选择之前请求锁但被拒绝的goroutine,而不是等待队列中的其他goroutine。这样,被拒绝的goroutine有机会尽快获取锁,避免了其他goroutine持有锁的时间过长而导致的不公平性。
通过调用Mutex的方法Lock()和Unlock()来获取和释放锁。示例如下:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var mu sync.Mutex
for i := 0; i < 10; i++ {
go func() {
mu.Lock()
defer mu.Unlock()
fmt.Println("goroutine", i, "got the lock")
time.Sleep(time.Second)
fmt.Println("goroutine", i, "released the lock")
}()
}
time.Sleep(5 * time.Second)
}
上面的示例中,我们创建了10个并发的goroutine,每个goroutine都会获取锁后打印一条消息,然后休眠1秒,最后释放锁。程序运行后,我们会看到goroutine的顺序是乱序的,这是因为Mutex是无序的,没有固定的顺序。
如果我们将Mutex的模式从正常模式更改为饥饿模式,可以通过修改Mutex的配置来实现。例如:
package main
import (
"fmt"
"sync"
"sync/atomic"
"time"
)
type starvingMutex struct {
sync.Mutex
}
func (m *starvingMutex) Lock() {
atomic.StoreInt32((*int32)(&m.Mutex), 1)
m.Mutex.Lock()
}
func (m *starvingMutex) Unlock() {
m.Mutex.Unlock()
atomic.StoreInt32((*int32)(&m.Mutex), 0)
}
func main() {
var mu starvingMutex
for i := 0; i < 10; i++ {
go func() {
mu.Lock()
defer mu.Unlock()
fmt.Println("goroutine", i, "got the lock")
time.Sleep(time.Second)
fmt.Println("goroutine", i, "released the lock")
}()
}
time.Sleep(5 * time.Second)
}
上面的示例中,我们自定义了一个starvingMutex类型,内嵌了sync.Mutex,并重写了Lock()和Unlock()方法。在Lock()方法中,我们先通过atomic.StoreInt32方法将Mutex的状态设置为1,然后再调用Mutex的Lock()方法。这样,之前请求锁但被拒绝的goroutine就有机会尽快获取锁。
注意,饥饿模式可能会导致更多的锁竞争和额外的性能开销,因此在大多数情况下,默认使用正常模式即可。只有在特定场景下才考虑切换到饥饿模式。