package main
import (
"fmt"
"sync/atomic"
"time"
)
type User struct {
Name string
LastLogin int64
}
func (u *AtomicUser) Store(key string, value *User) {
oldPtr := u.ptr.Load()
newMap := make(map[string]*User)
for k, v := range *oldPtr {
newMap[k] = v
}
newMap[key] = value
if u.ptr.CompareAndSwap(oldPtr, &newMap) {
return
}
}
type AtomicUser struct {
// 写的话就是
ptr atomic.Pointer[map[string]*User]
}
func NewAtomicUser() AtomicUser {
u := map[string]*User{}
atomicUser := AtomicUser{}
atomicUser.ptr.Store(&u)
return atomicUser
}
func (u *AtomicUser) Load() map[string]*User {
return *(u.ptr.Load())
}
var users = make(map[string]*User)
var atomicUsers = NewAtomicUser()
func main() {
// test1() // 出错
test2() // 可以一直读写
}
func test1() {
go func() {
for {
users["a"] = &User{Name: "a", LastLogin: time.Now().UnixNano()}
}
}()
go func() {
for {
fmt.Printf("a的name=%s\n", users["a"].Name)
time.Sleep(time.Millisecond * 200)
}
}()
for {
}
}
func test2() {
go func() {
for {
atomicUsers.Store("tom", &User{
Name: "zhagnsan1",
LastLogin: time.Now().UnixNano(),
})
}
}()
go func() {
for {
atomicUsers.Load()
fmt.Printf("a的name=%s,上次登录时间=%d\n", atomicUsers.Load()["tom"].Name, atomicUsers.Load()["tom"].LastLogin)
time.Sleep(time.Millisecond * 200)
}
}()
for {
}
}
上买呢test1()方法会执行一段时间后会因为读写同一个map出现panic
test2()通过atomic.Pointer来储存和获取值,底层是依赖CPU的原子执行的能力。对单独的变量进行原子访问控制的话可以用这个。
sync.Map 是依赖操作系统的api,性能开销更大。当然能力更强,如果有多个需要同步的操作,那么正常用mu.lock就可以了。
附上问答:原子读写性能高,那我是不是全都用原子读写就行了,不用mu.lock和mu.unlock了
以下为GPT回答
原子操作因为不需要使用复杂的锁机制可以提供高性能并且线程安全的读写能力,但是并不意味着在所有情况下都适合替代锁。原子操作通常用于更新某个独立的变量,而不涉及复杂的数据结构和状态。
当你要保护的是一些复杂的操作,比如多个变量的更新必须是原子性的,或者是某些操作组合到一起需要保持原子性(即要么全部发生,要么全部不发生),那么仅靠原子类型是无法保证正确性的。比如,你可能需要确保两个或者多个变量在同一时间内被更新,避免其他线程看到其中某一次更新的中间状态。在这种情况下,锁(比如mu.lock
和mu.unlock
)就变得非常重要了,因为它们可以保证一系列操作的原子性。
使用锁的缺点是可能会引入比原子操作更大的性能成本,因为锁会导致线程争用,并可能引起线程上下文切换。这是为什么在只有一个共享变量并且原子操作足以应对问题的情况下,原子操作是首选。总得来说,选择使用锁还是原子操作应该基于是否需要保护跨多个操作的复杂状态,以及相关操作的频率和性能需求。