面试题答案
一键面试避免内存竞争
- 使用互斥锁(Mutex):
- 当多个Goroutine需要访问共享资源时,通过互斥锁来保证同一时间只有一个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++ rwmu.Unlock() } func main() { var wg sync.WaitGroup for i := 0; i < 5; i++ { wg.Add(1) go read(&wg) } for i := 0; i < 2; i++ { wg.Add(1) go write(&wg) } wg.Wait() }
- 通道(Channel):
- 通过通道来传递数据,避免共享内存。Goroutine之间通过通道通信,数据会安全地从一个Goroutine传递到另一个,而不会产生竞争。例如:
package main import ( "fmt" ) func producer(ch chan int) { for i := 0; i < 5; i++ { ch <- i } close(ch) } func consumer(ch chan int) { for val := range ch { fmt.Println("Consumed:", val) } } func main() { ch := make(chan int) go producer(ch) go consumer(ch) select {} }
利用调度特性优化内存同步性能
- 减少锁的粒度:
- 尽量缩小锁保护的代码块范围。比如,在一个结构体有多个字段时,如果每个字段都可以独立修改,那么可以为每个字段分别加锁,而不是为整个结构体加锁。
package main import ( "fmt" "sync" ) type Fields struct { field1 int field2 int mu1 sync.Mutex mu2 sync.Mutex } func (f *Fields) updateField1() { f.mu1.Lock() f.field1++ f.mu1.Unlock() } func (f *Fields) updateField2() { f.mu2.Lock() f.field2++ f.mu2.Unlock() }
- 使用无锁数据结构:
- 对于一些简单的数据结构,Go标准库提供了无锁实现,如
sync/atomic
包。例如,原子操作可以高效地进行计数器的增减,避免锁的开销。
package main import ( "fmt" "sync" "sync/atomic" ) var counter uint64 func increment(wg *sync.WaitGroup) { defer wg.Done() atomic.AddUint64(&counter, 1) } func main() { var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go increment(&wg) } wg.Wait() fmt.Println("Final counter:", atomic.LoadUint64(&counter)) }
- 对于一些简单的数据结构,Go标准库提供了无锁实现,如
常见陷阱
- 死锁:
- 当多个Goroutine互相等待对方释放锁时会发生死锁。例如,两个Goroutine分别持有一个锁,并试图获取对方持有的锁。
package main import ( "fmt" "sync" ) var ( mu1 sync.Mutex mu2 sync.Mutex ) func goroutine1() { mu1.Lock() fmt.Println("Goroutine 1 acquired mu1") mu2.Lock() fmt.Println("Goroutine 1 acquired mu2") mu2.Unlock() mu1.Unlock() } func goroutine2() { mu2.Lock() fmt.Println("Goroutine 2 acquired mu2") mu1.Lock() fmt.Println("Goroutine 2 acquired mu1") mu1.Unlock() mu2.Unlock() } func main() { go goroutine1() go goroutine2() select {} }
- 这个程序会发生死锁,因为
goroutine1
获取mu1
后等待mu2
,而goroutine2
获取mu2
后等待mu1
。
- 误判通道关闭:
- 在使用
for... range
遍历通道时,如果在不恰当的时候关闭通道,可能会导致数据丢失或程序逻辑错误。例如,在生产者还未完全发送完数据时就关闭通道。
package main import ( "fmt" ) func producer(ch chan int) { for i := 0; i < 5; i++ { ch <- i if i == 2 { close(ch) // 错误关闭,数据还未完全发送 } } } func consumer(ch chan int) { for val := range ch { fmt.Println("Consumed:", val) } } func main() { ch := make(chan int) go producer(ch) go consumer(ch) select {} }
- 在这个例子中,生产者在发送2个数据后就关闭了通道,导致剩余数据无法发送给消费者。
- 在使用