面试题答案
一键面试在Go语言高并发系统中,数据竞争是指多个并发的goroutine同时读写共享数据,导致最终结果不可预测的问题。atomic
包提供了一系列原子操作函数,这些函数以原子的方式对共享数据进行操作,从而避免数据竞争。
原子操作是指不可中断的操作,在执行过程中不会被其他goroutine打断。这意味着,当一个goroutine执行原子操作时,其他goroutine无法同时访问被操作的数据,从而保证了数据的一致性。
常见的数据竞争场景及原子类型的解决方案
- 计数器场景
- 场景描述:多个goroutine同时对一个计数器变量进行加1操作。
- 代码示例(未使用原子操作,存在数据竞争):
package main
import (
"fmt"
"sync"
)
var counter int
var wg sync.WaitGroup
func increment() {
defer wg.Done()
counter++
}
func main() {
for i := 0; i < 1000; i++ {
wg.Add(1)
go increment()
}
wg.Wait()
fmt.Println("Final counter value:", counter)
}
- 分析:由于
counter++
不是原子操作,多个goroutine同时执行时,可能会出现读取、修改、写入的操作顺序不一致,导致最终结果小于1000。 - 使用原子操作解决:
package main
import (
"fmt"
"sync"
"sync/atomic"
)
var counter int64
var wg sync.WaitGroup
func increment() {
defer wg.Done()
atomic.AddInt64(&counter, 1)
}
func main() {
for i := 0; i < 1000; i++ {
wg.Add(1)
go increment()
}
wg.Wait()
fmt.Println("Final counter value:", atomic.LoadInt64(&counter))
}
- 分析:
atomic.AddInt64
函数以原子方式对counter
进行加1操作,确保每个goroutine的操作不会相互干扰,最终结果为1000。
- 标志位场景
- 场景描述:一个goroutine设置一个标志位,其他goroutine读取该标志位。
- 代码示例(未使用原子操作,存在数据竞争):
package main
import (
"fmt"
"sync"
)
var flag bool
var wg sync.WaitGroup
func setFlag() {
defer wg.Done()
flag = true
}
func checkFlag() {
defer wg.Done()
if flag {
fmt.Println("Flag is set")
} else {
fmt.Println("Flag is not set")
}
}
func main() {
wg.Add(2)
go setFlag()
go checkFlag()
wg.Wait()
}
- 分析:由于
flag
的读写不是原子操作,checkFlag
可能在setFlag
尚未完全设置flag
为true
时就进行读取,导致结果不可预测。 - 使用原子操作解决:
package main
import (
"fmt"
"sync"
"sync/atomic"
)
var flag int32
var wg sync.WaitGroup
func setFlag() {
defer wg.Done()
atomic.StoreInt32(&flag, 1)
}
func checkFlag() {
defer wg.Done()
if atomic.LoadInt32(&flag) == 1 {
fmt.Println("Flag is set")
} else {
fmt.Println("Flag is not set")
}
}
func main() {
wg.Add(2)
go setFlag()
go checkFlag()
wg.Wait()
}
- 分析:
atomic.StoreInt32
和atomic.LoadInt32
函数以原子方式进行标志位的设置和读取,保证了数据的一致性。
通过使用atomic
包中的函数,在高并发场景下可以有效地避免数据竞争问题,确保共享数据的一致性和正确性。