面试题答案
一键面试利用channel进行同步减少Mutex锁依赖
- 原理:
- Go语言的channel是一种类型安全的通信机制,可用于同步goroutine。通过在channel上发送和接收数据,可以实现同步操作,避免或减少对Mutex锁的需求。
- 例如,使用无缓冲channel进行同步,发送操作会阻塞直到有接收者准备好接收数据,接收操作会阻塞直到有发送者准备好发送数据,这一特性可以用于协调goroutine的执行顺序。
- 代码示例:
在这个示例中,生产者通过package main import ( "fmt" "sync" "time" ) func main() { var wg sync.WaitGroup dataCh := make(chan int) // 生产者 wg.Add(1) go func() { defer wg.Done() for i := 0; i < 5; i++ { dataCh <- i time.Sleep(time.Millisecond * 100) } close(dataCh) }() // 消费者 wg.Add(1) go func() { defer wg.Done() for data := range dataCh { fmt.Println("Received:", data) time.Sleep(time.Millisecond * 100) } }() wg.Wait() }
dataCh
向消费者发送数据,消费者从dataCh
接收数据。通过channel的同步机制,实现了生产者和消费者之间的协调,而无需使用Mutex锁。
性能对比分析
- 使用Mutex锁的示例:
package main import ( "fmt" "sync" "time" ) var mu sync.Mutex var sharedData int func main() { var wg sync.WaitGroup for i := 0; i < 5; i++ { wg.Add(1) go func(id int) { defer wg.Done() mu.Lock() sharedData += id fmt.Printf("Goroutine %d updated sharedData to %d\n", id, sharedData) mu.Unlock() time.Sleep(time.Millisecond * 100) }(i) } wg.Wait() }
- 性能对比:
- 测试场景:假设多个goroutine需要访问和修改共享资源。
- 性能差异:
- Mutex锁:当goroutine数量增多时,锁争用会加剧,因为每次只有一个goroutine能获取到Mutex锁来访问共享资源,其他goroutine需要等待,这会导致额外的上下文切换开销,降低整体性能。
- Channel同步:使用channel进行同步,每个goroutine之间通过channel通信,不需要像Mutex锁那样独占式地访问共享资源,减少了锁争用的可能性,在高并发场景下通常能有更好的性能表现。例如,在上述代码中,如果有大量的goroutine同时尝试修改共享数据,使用Mutex锁会有明显的等待时间,而使用channel同步的方式,生产者和消费者能更流畅地进行数据传递和处理,不会因为锁争用而产生等待。
其他高级技巧
- 读写锁(RWMutex):
- 原理:如果应用场景中读操作远多于写操作,可以使用读写锁。读写锁允许多个读操作同时进行,但写操作必须独占。这样可以提高读操作的并发性能。
- 代码示例:
package main import ( "fmt" "sync" ) var mu sync.RWMutex var sharedData int func readData(id int, wg *sync.WaitGroup) { defer wg.Done() mu.RLock() fmt.Printf("Goroutine %d read sharedData: %d\n", id, sharedData) mu.RUnlock() } func writeData(id int, wg *sync.WaitGroup) { defer wg.Done() mu.Lock() sharedData += id fmt.Printf("Goroutine %d updated sharedData to %d\n", id, sharedData) mu.Unlock() } func main() { var wg sync.WaitGroup for i := 0; i < 3; i++ { wg.Add(1) go readData(i, &wg) } for i := 0; i < 2; i++ { wg.Add(1) go writeData(i, &wg) } wg.Wait() }
- 分段锁:
- 原理:将共享资源分成多个段,每个段使用独立的Mutex锁。这样不同段的操作可以并发进行,减少锁争用。
- 代码示例:
在这个示例中,将共享资源分为3个段,每个段有自己的锁,不同段的更新操作可以并发执行,减少了锁争用。package main import ( "fmt" "sync" ) type Segment struct { data int mutex sync.Mutex } func updateSegment(segment *Segment, id int, wg *sync.WaitGroup) { defer wg.Done() segment.mutex.Lock() segment.data += id fmt.Printf("Goroutine %d updated segment data to %d\n", id, segment.data) segment.mutex.Unlock() } func main() { segments := make([]Segment, 3) var wg sync.WaitGroup for i := 0; i < 5; i++ { wg.Add(1) go updateSegment(&segments[i%3], i, &wg) } wg.Wait() }