面试题答案
一键面试问题可能出现的场景
- 资源竞争场景:
- 共享数据读写:当多个 goroutine 同时访问和修改共享数据时,例如一个全局变量或共享的结构体字段。假设在一个电商应用中,多个订单处理 goroutine 同时读取和更新商品库存变量,就可能导致库存数据不一致。
- I/O 资源竞争:如多个 goroutine 同时向同一个文件或网络连接写入数据。例如在日志记录场景,多个 goroutine 尝试同时向日志文件写入日志,可能造成日志内容错乱。
- 死锁场景:
- 循环依赖:假设存在 A、B 两个 goroutine,A 等待 B 释放资源 X,B 等待 A 释放资源 Y,形成循环等待,导致死锁。例如在分布式系统中,两个微服务之间相互调用对方的资源,并且都在等待对方先释放锁。
- 锁使用不当:在一个复杂的业务逻辑中,如果在一个 goroutine 中先获取锁 L1,然后试图获取锁 L2,而在另一个 goroutine 中以相反的顺序获取锁(先获取 L2,再获取 L1),就可能导致死锁。
解决方案
- 使用 sync 包中的工具:
- 互斥锁(Mutex):用于保护共享资源,确保同一时间只有一个 goroutine 可以访问。
package main import ( "fmt" "sync" ) var ( counter int mu sync.Mutex ) func increment(wg *sync.WaitGroup) { defer wg.Done() mu.Lock() counter++ mu.Unlock() } func main() { var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go increment(&wg) } wg.Wait() fmt.Println("Final counter:", counter) }
- 读写锁(RWMutex):适用于读多写少的场景,允许多个 goroutine 同时读,但写操作时会独占资源。
package main import ( "fmt" "sync" ) var ( data int rwmu sync.RWMutex ) func read(wg *sync.WaitGroup) { defer wg.Done() rwmu.RLock() fmt.Println("Read data:", data) rwmu.RUnlock() } func write(wg *sync.WaitGroup) { defer wg.Done() rwmu.Lock() data++ fmt.Println("Write data:", data) rwmu.Unlock() } func main() { var wg sync.WaitGroup for i := 0; i < 5; i++ { if i%2 == 0 { wg.Add(1) go read(&wg) } else { wg.Add(1) go write(&wg) } } wg.Wait() }
- 使用 channel:
- 无缓冲 channel:可以用于 goroutine 之间的同步。例如,一个 goroutine 发送数据到无缓冲 channel,另一个 goroutine 接收数据,这就实现了同步操作。
package main import ( "fmt" ) func main() { ch := make(chan struct{}) go func() { fmt.Println("Goroutine is working") ch <- struct{}{} }() <-ch fmt.Println("Main goroutine received signal") }
- 有缓冲 channel:可以用于解耦生产者和消费者,并且可以限制并发数。例如,在一个任务处理系统中,生产者将任务发送到有缓冲 channel,消费者从 channel 中取出任务处理。
package main import ( "fmt" "sync" ) func worker(id int, tasks <-chan int, wg *sync.WaitGroup) { defer wg.Done() for task := range tasks { fmt.Printf("Worker %d is processing task %d\n", id, task) } } func main() { var wg sync.WaitGroup tasks := make(chan int, 10) for i := 0; i < 3; i++ { wg.Add(1) go worker(i, tasks, &wg) } for i := 0; i < 10; i++ { tasks <- i } close(tasks) wg.Wait() }
通过合理使用这些工具,可以在保证接口设计灵活性的前提下,有效地解决高并发应用中的资源竞争和死锁问题,实现架构的高可扩展性。