面试题答案
一键面试原子操作在Go内存模型下保证数据一致性的原理
- 原子性:Go的原子操作(如
atomic
包中的函数)提供了不可分割的操作。例如,对一个整数的原子加法操作,在执行过程中不会被其他协程打断,这保证了操作的完整性。 - 内存同步:原子操作会在内存层面产生同步效果。Go内存模型规定,对原子变量的写操作会在其后的读操作之前发生(happens-before),这确保了不同协程间对该变量操作的顺序性和可见性。例如,一个协程对原子变量进行写操作,后续其他协程对该变量的读操作一定能看到这个写操作的结果。
数据不一致导致问题的场景
假设有多个协程同时对一个共享的计数器进行加一操作。
package main
import (
"fmt"
)
var counter int
func increment() {
counter = counter + 1
}
func main() {
var numRoutines = 1000
for i := 0; i < numRoutines; i++ {
go increment()
}
// 这里简单等待一会儿,实际应用中需要更好的同步机制
// 此等待并不能保证所有协程执行完毕
fmt.Println("Final counter value:", counter)
}
在这个例子中,由于counter = counter + 1
不是原子操作,多个协程同时执行时,可能会出现竞态条件(race condition)。例如,两个协程同时读取counter
的值,然后分别加一,最后再写回,这样就会丢失一次加一操作,导致最终的counter
值小于预期的1000。
使用原子操作解决问题
package main
import (
"fmt"
"sync"
"sync/atomic"
)
var counter int64
func increment(wg *sync.WaitGroup) {
defer wg.Done()
atomic.AddInt64(&counter, 1)
}
func main() {
var numRoutines = 1000
var wg sync.WaitGroup
wg.Add(numRoutines)
for i := 0; i < numRoutines; i++ {
go increment(&wg)
}
wg.Wait()
fmt.Println("Final counter value:", atomic.LoadInt64(&counter))
}
在这个改进的代码中,使用atomic.AddInt64
进行原子加法操作,保证了每次加一操作的原子性,避免了竞态条件,从而确保最终的counter
值为1000。同时,使用sync.WaitGroup
来等待所有协程执行完毕,确保输出的结果是所有协程操作完成后的最终值。