面试题答案
一键面试设计思路
- 原子操作:对于简单的数据类型(如整数、指针等),使用Go语言的原子包(
sync/atomic
)进行操作,以保证在多goroutine环境下数据的一致性。 - 通道(Channel):用于在goroutine之间传递数据和同步。通过通道来协调读写操作,避免数据竞争。
- 读写锁:对于更复杂的数据结构,使用读写锁(
sync.RWMutex
)。读操作可以并发执行,写操作则需要独占锁,以保证数据一致性。
关键代码实现
package main
import (
"fmt"
"sync"
"sync/atomic"
)
// 假设共享数据为一个整数
var sharedData int64
// 用于同步的通道
var readCh = make(chan struct{})
var writeCh = make(chan struct{})
func reader(wg *sync.WaitGroup, id int) {
defer wg.Done()
readCh <- struct{}{} // 通知可以开始读操作
data := atomic.LoadInt64(&sharedData)
<-readCh // 通知读操作完成
fmt.Printf("Reader %d read data: %d\n", id, data)
}
func writer(wg *sync.WaitGroup, id int, newData int64) {
defer wg.Done()
writeCh <- struct{}{} // 通知可以开始写操作
atomic.StoreInt64(&sharedData, newData)
<-writeCh // 通知写操作完成
fmt.Printf("Writer %d wrote data: %d\n", id, newData)
}
确保线程安全性
- 原子操作:通过
atomic.LoadInt64
和atomic.StoreInt64
函数,保证对sharedData
的读写操作是原子的,不会出现数据竞争。 - 通道同步:读操作和写操作通过通道
readCh
和writeCh
进行同步。读操作开始前向通道发送信号,完成后从通道接收信号;写操作同理。这样可以确保读写操作不会同时进行,保证数据一致性。 - 读写锁(如果使用复杂数据结构):如果共享数据是复杂数据结构,在读写操作前获取相应的读写锁。读操作获取读锁,允许多个读操作并发;写操作获取写锁,独占数据,防止其他读写操作同时进行。
在主函数中使用示例:
func main() {
var wg sync.WaitGroup
// 启动多个读操作
for i := 0; i < 3; i++ {
wg.Add(1)
go reader(&wg, i)
}
// 启动多个写操作
for i := 0; i < 2; i++ {
wg.Add(1)
go writer(&wg, i, int64(i*10))
}
wg.Wait()
}
这样通过原子操作、通道同步以及读写锁(根据实际情况),可以在多goroutine环境下保证共享数据的一致性和线程安全性,同时尽可能提高并发性能。