面试题答案
一键面试互斥锁(sync.Mutex)
- 优点:
- 实现简单,逻辑清晰。无论读操作还是写操作,都通过加锁来保证同一时间只有一个 goroutine 能访问共享资源,能很好地保证数据一致性。
- 缺点:
- 在高并发读多写少的场景下,性能较差。因为读操作也会被锁限制,即使读操作之间不会产生数据竞争,也不能并行执行,这会导致大量读操作等待锁释放,降低了程序的并发性能。
读写锁(sync.RWMutex)
- 优点:
- 在读多写少的场景下性能优势明显。读操作可以并发执行,只要没有写操作在进行,多个读操作可以同时获取读锁,大大提高了读操作的并发度。写操作则独占锁,保证数据一致性。
- 缺点:
- 实现相对复杂,需要考虑读锁和写锁的不同使用场景。并且写操作时会阻塞所有读操作,当写操作频繁时,可能会导致读操作长时间等待。
性能测试
- 测试互斥锁性能:
package main
import (
"fmt"
"sync"
"time"
)
var mu sync.Mutex
var data int
func readMutex(wg *sync.WaitGroup) {
defer wg.Done()
mu.Lock()
_ = data
mu.Unlock()
}
func writeMutex(wg *sync.WaitGroup) {
defer wg.Done()
mu.Lock()
data++
mu.Unlock()
}
func main() {
var wg sync.WaitGroup
start := time.Now()
for i := 0; i < 1000; i++ {
if i%100 == 0 {
wg.Add(1)
go writeMutex(&wg)
} else {
wg.Add(1)
go readMutex(&wg)
}
}
wg.Wait()
elapsed := time.Since(start)
fmt.Printf("互斥锁总耗时: %s\n", elapsed)
}
- 测试读写锁性能:
package main
import (
"fmt"
"sync"
"time"
)
var rwmu sync.RWMutex
var data2 int
func readRWMutex(wg *sync.WaitGroup) {
defer wg.Done()
rwmu.RLock()
_ = data2
rwmu.RUnlock()
}
func writeRWMutex(wg *sync.WaitGroup) {
defer wg.Done()
rwmu.Lock()
data2++
rwmu.Unlock()
}
func main() {
var wg sync.WaitGroup
start := time.Now()
for i := 0; i < 1000; i++ {
if i%100 == 0 {
wg.Add(1)
go writeRWMutex(&wg)
} else {
wg.Add(1)
go readRWMutex(&wg)
}
}
wg.Wait()
elapsed := time.Since(start)
fmt.Printf("读写锁总耗时: %s\n", elapsed)
}
通过上述代码分别测试互斥锁和读写锁在相同读多写少场景下的总耗时,对比两者耗时长短,若读写锁耗时明显短于互斥锁,则证明在该场景下读写锁是更优选择。