4.3 示例代码
在本节中,我们将展示一些示例代码,通过使用 sync/atomic 包的原子操作函数来实现并发安全的操作。这些示例包括计数器的递增、标志位的设置、指针的操作等,帮助您更好地理解原子操作的应用场景和使用方法。
4.3.1 原子计数器示例
以下代码展示了如何使用 atomic.AddInt32 实现并发安全的计数器递增操作:
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var counter int32 = 0
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 1000; j++ {
atomic.AddInt32(&counter, 1)
}
}()
}
wg.Wait()
fmt.Printf("Final counter value: %d\n", counter)
}
在这个示例中,10 个 goroutine 并发地递增 counter,每个 goroutine 执行 1000 次递增操作。使用 atomic.AddInt32 确保了操作的原子性,最终 counter 的值应为 10000。
4.3.2 原子标志位示例
以下代码展示了如何使用 atomic.StoreInt32 和 atomic.LoadInt32 实现并发安全的标志位设置和读取操作:
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var flag int32 = 0
var wg sync.WaitGroup
// Goroutine to set the flag
wg.Add(1)
go func() {
defer wg.Done()
atomic.StoreInt32(&flag, 1)
fmt.Println("Flag set to 1")
}()
// Goroutine to check the flag
wg.Add(1)
go func() {
defer wg.Done()
for {
if atomic.LoadInt32(&flag) == 1 {
fmt.Println("Flag is 1, proceeding")
break
}
}
}()
wg.Wait()
}
在这个示例中,第一个 goroutine 将标志位 flag 设置为 1,第二个 goroutine 通过轮询检查 flag 的值,当 flag 为 1 时,打印一条消息并退出。
4.3.3 原子指针操作示例
以下代码展示了如何使用 atomic.LoadPointer 和 atomic.StorePointer 实现并发安全的指针操作:
package main
import (
"fmt"
"sync"
"sync/atomic"
"unsafe"
)
func main() {
var ptr unsafe.Pointer
var wg sync.WaitGroup
// Goroutine to set the pointer
wg.Add(1)
go func() {
defer wg.Done()
str := "Hello, World!"
atomic.StorePointer(&ptr, unsafe.Pointer(&str))
fmt.Println("Pointer set to:", str)
}()
// Goroutine to read the pointer
wg.Add(1)
go func() {
defer wg.Done()
for {
p := atomic.LoadPointer(&ptr)
if p != nil {
fmt.Println("Pointer value:", *(*string)(p))
break
}
}
}()
wg.Wait()
}
在这个示例中,第一个 goroutine 将字符串指针 str 设置为 ptr,第二个 goroutine 通过轮询检查 ptr 的值,当 ptr 不为 nil 时,打印指针所指向的字符串并退出。
结论
通过这些示例代码,我们展示了如何使用 Go 语言的 sync/atomic 包实现并发安全的原子操作。这些示例涵盖了计数器的递增、标志位的设置和读取、以及指针的操作,展示了原子操作在实际应用中的多种场景。在并发编程中,原子操作是确保数据一致性和避免竞态条件的重要手段。在接下来的章节中,我们将继续探讨其他同步原语和并发编程技巧,帮助您更好地掌握 Go 的并发编程。