面试题答案
一键面试原子操作常见类型
- 加法操作:像
atomic.AddInt64
用于对int64
类型的变量进行原子加法操作。在并发环境下,对数值类型变量的增减操作如果不使用原子操作,可能会出现竞态条件。 - 比较并交换(CAS)操作:
atomic.CompareAndSwapInt64
等。它先比较内存中的值与给定的旧值,如果相等则将其替换为新值。常用于实现无锁数据结构,例如链表的插入删除操作。 - 加载操作:如
atomic.LoadInt64
,原子地加载int64
类型变量的值,确保读取到的值是完整且未被并发修改影响的。 - 存储操作:
atomic.StoreInt64
原子地存储一个int64
值到指定内存地址,保证存储操作的原子性,避免并发写冲突。
如何确保并发安全
Go语言的原子操作是通过底层硬件提供的原子指令来实现的。这些指令在硬件层面保证了操作的原子性,即操作要么完整执行,要么不执行,不会出现部分执行的情况。在多处理器系统中,这些指令还通过内存屏障(Memory Barrier)来保证内存一致性,确保不同处理器对共享内存的访问顺序符合预期,避免出现内存可见性问题。
以 atomic.AddInt64
为例说明
atomic.AddInt64
函数原型为 func AddInt64(addr *int64, delta int64) (new int64)
。它原子地将 delta
添加到 addr
指向的 int64
变量上,并返回新的值。当多个 goroutine 同时调用 atomic.AddInt64
对同一个 int64
变量进行加法操作时,由于底层硬件的原子指令保证,不会出现一个 goroutine 读取了变量的值,另一个 goroutine 也读取了相同值并进行加法操作,导致结果错误的情况。每个加法操作都是原子的,确保了并发环境下对该变量加法操作的正确性和安全性。例如:
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var num int64
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
atomic.AddInt64(&num, 1)
}()
}
wg.Wait()
fmt.Println("Final value:", num)
}
在上述代码中,10 个 goroutine 并发地对 num
进行加法操作,使用 atomic.AddInt64
能确保最终 num
的值为 10,而不会因为并发问题导致值错误。