面试题答案
一键面试- 使用
sync.Mutex
保护共享资源:- 在读写共享资源时,通过
sync.Mutex
来加锁。读操作和写操作前都获取锁,操作完成后释放锁。这样可以避免数据竞争。例如:
package main import ( "fmt" "sync" ) var ( data int mu sync.Mutex ) func writer(wg *sync.WaitGroup) { defer wg.Done() mu.Lock() data++ mu.Unlock() } func reader(wg *sync.WaitGroup) { defer wg.Done() mu.Lock() fmt.Println("Read data:", data) mu.Unlock() } func main() { var wg sync.WaitGroup for i := 0; i < 5; i++ { wg.Add(1) go writer(&wg) } for i := 0; i < 5; i++ { wg.Add(1) go reader(&wg) } wg.Wait() }
- 在读写共享资源时,通过
- 使用
sync.RWMutex
(适用于读多写少场景):- 读操作使用读锁(
RLock
),写操作使用写锁(Lock
)。读锁允许多个读操作同时进行,写锁则独占资源,避免读写冲突和写写冲突。例如:
package main import ( "fmt" "sync" ) var ( data int mu sync.RWMutex ) func writer(wg *sync.WaitGroup) { defer wg.Done() mu.Lock() data++ mu.Unlock() } func reader(wg *sync.WaitGroup) { defer wg.Done() mu.RLock() fmt.Println("Read data:", data) mu.RUnlock() } func main() { var wg sync.WaitGroup for i := 0; i < 5; i++ { wg.Add(1) go writer(&wg) } for i := 0; i < 5; i++ { wg.Add(1) go reader(&wg) } wg.Wait() }
- 读操作使用读锁(
- 使用
sync.WaitGroup
等待所有Goroutine结束:- 在主Goroutine中创建一个
sync.WaitGroup
实例。每个Goroutine开始前调用wg.Add(1)
,在Goroutine结束时调用wg.Done()
。主Goroutine通过wg.Wait()
等待所有Goroutine完成。这样可以确保在共享资源不再被使用时进行回收。如上述示例中,在main
函数中创建wg
并在每个Goroutine中相应调用Add
、Done
和Wait
方法。
- 在主Goroutine中创建一个
- 使用
sync.Cond
(可选,用于更复杂的同步场景):- 当需要在满足某个条件时才进行资源回收或某些操作时,可以使用
sync.Cond
。sync.Cond
需要与一个Locker
(如sync.Mutex
)结合使用。例如,假设资源需要在所有Goroutine完成某种特定操作后才能回收:
package main import ( "fmt" "sync" ) var ( data int mu sync.Mutex cond *sync.Cond counter int ) func worker(wg *sync.WaitGroup) { defer wg.Done() mu.Lock() // 模拟一些操作 data++ counter++ if counter == 10 { cond.Broadcast() } mu.Unlock() } func main() { var wg sync.WaitGroup cond = sync.NewCond(&mu) for i := 0; i < 10; i++ { wg.Add(1) go worker(&wg) } mu.Lock() for counter < 10 { cond.Wait() } // 在这里可以安全地回收资源 fmt.Println("All workers finished, safe to recycle resources.") mu.Unlock() wg.Wait() }
- 当需要在满足某个条件时才进行资源回收或某些操作时,可以使用
通过以上sync
包工具的合理使用,可以确保在Goroutine生命周期结束时共享资源的正确回收并避免数据竞争问题。