面试题答案
一键面试原子类型
- 适用场景:适用于对简单数据类型(如整数、布尔值等)进行原子操作,在低并发且对单个变量进行简单读写的场景下表现出色。
- 性能特点:在低并发场景下,原子操作开销极小,因为它直接在硬件层面提供原子性保证,不需要额外的上下文切换等开销。但在高并发下,随着竞争加剧,由于缓存一致性协议的开销,性能提升有限。
- 示例代码:
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var counter int64
var wg sync.WaitGroup
numRoutines := 1000
for i := 0; i < numRoutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
atomic.AddInt64(&counter, 1)
}()
}
wg.Wait()
fmt.Println("Final counter value:", counter)
}
- 分析:在这个示例中,通过原子操作
atomic.AddInt64
对counter
变量进行累加,在多协程环境下保证了数据的一致性。由于原子操作直接在硬件层面实现,对于简单数据类型的操作效率很高。
互斥锁(sync.Mutex)
- 适用场景:适用于读写比例不确定,并发程度较高,需要对一段代码或数据结构进行独占访问的场景。
- 性能特点:互斥锁提供了最基本的同步机制,确保同一时间只有一个协程能访问临界区。在高并发写操作频繁的场景下,由于锁的竞争,会导致性能下降,因为其他协程需要等待锁的释放。读操作也会受到写操作锁的影响。
- 示例代码:
package main
import (
"fmt"
"sync"
)
func main() {
var counter int
var mu sync.Mutex
var wg sync.WaitGroup
numRoutines := 1000
for i := 0; i < numRoutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
mu.Lock()
counter++
mu.Unlock()
}()
}
wg.Wait()
fmt.Println("Final counter value:", counter)
}
- 分析:这里通过
mu.Lock()
和mu.Unlock()
来保护counter
变量的修改,确保同一时间只有一个协程能修改counter
。在高并发下,如果写操作频繁,锁的竞争会成为性能瓶颈。
读写锁(sync.RWMutex)
- 适用场景:适用于读操作远多于写操作的场景,读操作可以并发执行,而写操作需要独占访问。
- 性能特点:读锁允许并发读取,提高了读操作的性能。但写锁会独占资源,在写操作时,其他读写操作都需要等待。在读写比例合适的情况下,能显著提升性能。
- 示例代码:
package main
import (
"fmt"
"sync"
)
func main() {
var data int
var rwmu sync.RWMutex
var wg sync.WaitGroup
// 模拟读操作
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
rwmu.RLock()
fmt.Println("Read data:", data)
rwmu.RUnlock()
}()
}
// 模拟写操作
for i := 0; i < 2; i++ {
wg.Add(1)
go func() {
defer wg.Done()
rwmu.Lock()
data++
fmt.Println("Write data:", data)
rwmu.Unlock()
}()
}
wg.Wait()
}
- 分析:读操作使用
rwmu.RLock()
和rwmu.RUnlock()
,允许多个读操作并发执行。写操作使用rwmu.Lock()
和rwmu.Unlock()
,确保写操作的原子性。在高读低写场景下,读写锁能有效提升性能。
同步机制选择建议
- 低并发且简单变量操作:优先选择原子类型,性能最佳。
- 读写比例不确定且并发高:使用互斥锁,提供简单有效的同步。
- 读多写少场景:选择读写锁,提升读操作性能。
通过实际的性能测试(如使用time
包统计不同同步机制在不同并发数、读写比例下的运行时间),可以更准确地评估和选择最适合业务场景的同步机制。