在 Go 语言中主要有以下几种锁:
**一、互斥锁(sync.Mutex)**
1. 用途:
- 用于保证在同一时刻只有一个协程能够访问被保护的资源,防止出现数据竞争。这个是一个悲观锁
- 常用于多个协程需要对共享变量进行读写操作的场景。
2. 示例代码:
package main
import (
"fmt"
"sync"
)
var count int
var mutex sync.Mutex
func increment() {
mutex.Lock()
count++
mutex.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
increment()
}()
}
wg.Wait()
fmt.Println(count)
}
在这个例子中,多个协程同时调用`increment`函数对共享变量`count`进行递增操作,通过互斥锁`sync.Mutex`保证了操作的原子性,避免了数据竞争。
**二、读写锁(sync.RWMutex)**
1. 用途:
- 允许多个协程同时进行读操作,但在写操作时需要独占访问。
- 适用于读多写少的场景,可以提高并发性能。
2. 示例代码:
package main
import (
"fmt"
"sync"
)
var data int
var rwMutex sync.RWMutex
func readData() int {
rwMutex.RLock()
defer rwMutex.RUnlock()
return data
}
func writeData(newData int) {
rwMutex.Lock()
data = newData
rwMutex.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(readData())
}()
}
wg.Add(1)
go func() {
defer wg.Done()
writeData(42)
}()
wg.Wait()
}
这里多个协程可以同时调用`readData`进行读操作,而写操作`writeData`会独占访问资源。
`sync.Mutex`(互斥锁)和`sync.RWMutex`(读写锁)的主要区别如下:
**一、功能特性**
1. `sync.Mutex`:
- 提供了基本的互斥功能,同一时刻只允许一个协程持有锁进行访问。
- 无论是读操作还是写操作,只要一个协程获取了锁,其他协程无论是读还是写都必须等待。
2. `sync.RWMutex`:
- 区分了读锁和写锁。
- 允许多个协程同时持有读锁进行读操作。
- 当有一个协程持有写锁时,其他协程无论是读还是写都必须等待。
**二、性能表现**
1. 读多写少的场景:
- 如果一个程序中读操作远远多于写操作,使用`sync.RWMutex`性能会更好。因为多个读操作可以同时进行,不会像`sync.Mutex`那样每次读操作也需要等待锁的释放。
- 例如,一个缓存系统,经常被多个协程读取数据,而写入数据的频率相对较低。在这种情况下,使用读写锁可以提高并发度,减少等待时间。
2. 写操作频繁的场景:
- 如果写操作比较频繁,两种锁的性能差异可能不明显,甚至在某些情况下`sync.Mutex`可能表现更好。
- 因为`sync.RWMutex`在获取写锁时需要等待所有的读锁释放,这可能会导致一定的延迟。
**三、代码示例**
以下是一个更直观的代码对比:
package main
import (
"fmt"
"sync"
"time"
)
// 使用互斥锁
func mutexExample() {
var mutex sync.Mutex
var value int
// 模拟多个协程同时读和写
for i := 0; i < 5; i++ {
go func(id int) {
mutex.Lock()
fmt.Printf("协程 %d 读取值:%d\n", id, value)
value++
fmt.Printf("协程 %d 写入值后:%d\n", id, value)
mutex.Unlock()
}(i)
}
}
// 使用读写锁
func rwMutexExample() {
var rwMutex sync.RWMutex
var value int
// 模拟多个协程同时读和写
for i := 0; i < 5; i++ {
if i%2 == 0 {
// 写操作
go func(id int) {
rwMutex.Lock()
fmt.Printf("协程 %d 写入值前:%d\n", id, value)
value++
fmt.Printf("协程 %d 写入值后:%d\n", id, value)
rwMutex.Unlock()
}(i)
} else {
// 读操作
go func(id int) {
rwMutex.RLock()
fmt.Printf("协程 %d 读取值:%d\n", id, value)
rwMutex.RUnlock()
}(i)
}
}
}
func main() {
// 测试互斥锁
fmt.Println("互斥锁示例:")
mutexExample()
time.Sleep(time.Second)
// 测试读写锁
fmt.Println("读写锁示例:")
rwMutexExample()
time.Sleep(time.Second)
}
在这个例子中,可以观察到两种锁在不同场景下的行为差异。读写锁允许同时进行读操作,而互斥锁在任何时候都只允许一个协程访问资源。