面试题答案
一键面试- 理解Go语言内存模型
- Go语言内存模型定义了在并发环境下,对共享变量的读操作和写操作之间的同步规则。只有当写操作“happens - before”读操作时,读操作才能保证读到最新的值。例如:
package main
import (
"fmt"
"sync"
)
var (
num int
once sync.Once
)
func read() {
once.Do(func() {
num = 10
})
fmt.Println(num)
}
这里once.Do
确保了对num
的写操作(设置为10)在fmt.Println(num)
读操作之前发生,所以能保证读到正确的值。
2. 避免数据竞争对指针性能的影响
- 使用互斥锁(
sync.Mutex
):- 当多个协程可能同时访问和修改指针指向的数据时,使用互斥锁来保护对指针的操作。例如:
package main
import (
"fmt"
"sync"
)
type Data struct {
value int
}
var (
dataPtr *Data
mu sync.Mutex
)
func updateData(newValue int) {
mu.Lock()
if dataPtr == nil {
dataPtr = &Data{}
}
dataPtr.value = newValue
mu.Unlock()
}
func readData() int {
mu.Lock()
if dataPtr == nil {
mu.Unlock()
return 0
}
value := dataPtr.value
mu.Unlock()
return value
}
- 使用读写锁(
sync.RWMutex
):- 如果有大量的读操作和少量的写操作,可以使用读写锁。读操作可以同时进行,写操作则需要独占锁。例如:
package main
import (
"fmt"
"sync"
)
type SharedData struct {
value int
}
var (
sharedPtr *SharedData
rwMu sync.RWMutex
)
func readShared() int {
rwMu.RLock()
if sharedPtr == nil {
rwMu.RUnlock()
return 0
}
value := sharedPtr.value
rwMu.RUnlock()
return value
}
func writeShared(newValue int) {
rwMu.Lock()
if sharedPtr == nil {
sharedPtr = &SharedData{}
}
sharedPtr.value = newValue
rwMu.Unlock()
}
- 合理使用原子操作
- 针对简单指针类型:
- 对于指针类型,可以使用
atomic
包中的函数(如atomic.CompareAndSwapPointer
)来实现原子操作。例如,在一个需要原子更新指针的场景中:
- 对于指针类型,可以使用
- 针对简单指针类型:
package main
import (
"fmt"
"sync"
"sync/atomic"
)
type Node struct {
data int
next *Node
}
var headPtr atomic.Pointer[Node]
func addNode(newNode *Node) {
for {
oldHead := headPtr.Load()
newNode.next = oldHead
if atomic.CompareAndSwapPointer(&headPtr, oldHead, newNode) {
break
}
}
}
func getHead() *Node {
return headPtr.Load()
}
- 注意事项:
- 原子操作通常适用于简单的指针更新操作。如果指针指向的数据结构复杂,原子操作可能无法完全避免数据竞争,还需要结合其他同步原语。
- 使用通道(
channel
)- 利用通道进行数据传递:
- 通过通道在协程之间传递指针,而不是直接共享指针。这样可以避免多个协程同时操作指针。例如:
- 利用通道进行数据传递:
package main
import (
"fmt"
"sync"
)
type Work struct {
data int
}
func worker(in chan *Work, wg *sync.WaitGroup) {
defer wg.Done()
for work := range in {
fmt.Println("Processing work:", work.data)
}
}
- 通道缓冲与性能:
- 合理设置通道的缓冲区大小可以优化性能。如果缓冲区过小,可能导致协程阻塞;缓冲区过大,可能浪费内存。例如:
in := make(chan *Work, 10)
,这里设置缓冲区大小为10。
- 合理设置通道的缓冲区大小可以优化性能。如果缓冲区过小,可能导致协程阻塞;缓冲区过大,可能浪费内存。例如:
通过上述方法,可以在Go语言并发环境下,结合内存模型优化指针性能,避免数据竞争,确保指针操作在高并发下的高效性。