package main
import (
"fmt"
"sync/atomic"
"unsafe"
)
func main(){
old := make(map[string]string)
new := make(map[string]string)
new["hello"] = "apple"
fmt.Println("start swap")
atomic.SwapPointer((*unsafe.Pointer)(unsafe.Pointer(&old)), unsafe.Pointer(&new))
fmt.Println("end swap")
// pending here, don't stop
fmt.Println(old)
fmt.Println("end print old")
}
我想要一种无锁的方式来用新地图更新旧地图,因为大部分时间都会同时读取旧地图。
如果我使用rwlock,将会受到严重的性能惩罚。
所以我选择Golang原子包来实现这一点,但是
fmt.Println(old),该程序被卡在此处,有人可以提供一些建议。
答案 0 :(得分:1)
函数atomic.SwapPointer
不能满足您的需求。正如the documentation所说,这大致等同于:
old = *addr
*addr = new
return old
(除了对*addr
的写操作和对*addr
的读操作是原子完成的)。我想您想要的是原子上的等价物:
*old, *new = *new, *old
(并且没有有用的返回值)。 sync
包中根本不存在此操作。如果这样做的话,您可以交换两个内部映射指针,但是您仍然会陷入危险(将来可能会崩溃的编译器),正如多位评论者指出的那样。
请考虑改用sync.Map
。它提供了一个具有并发安全性的映射,该映射具有内部(每个条目)锁定,该映射针对两个用例进行了优化,如链接的软件包文档中所述。如果您的用例是这两种情况之一,则可以满足您的需求。
仅供说明(不要这样做!这很愚蠢,您可以在*old, *new = *new, *old
中写swap
)... A non-atomic swap of old and new can be achieved using atomic.SwapPointer
:
package main
import (
"fmt"
"sync/atomic"
"unsafe"
)
func read(p unsafe.Pointer) unsafe.Pointer {
return *(*unsafe.Pointer)(p)
}
func swap(old *map[string]string, new *map[string]string) {
p := atomic.SwapPointer((*unsafe.Pointer)(unsafe.Pointer(old)), read(unsafe.Pointer(new)))
_ = atomic.SwapPointer((*unsafe.Pointer)(unsafe.Pointer(new)), p)
}
func main() {
old := map[string]string{"old": "old"}
new := map[string]string{"hello": "apple", "new": "new"}
fmt.Println("before: old =", old, "new =", new)
// fmt.Println("before: old:", read(unsafe.Pointer(&old)), "new:", read(unsafe.Pointer(&new)))
swap(&old, &new)
// fmt.Println("after: old:", read(unsafe.Pointer(&old)), "new:", read(unsafe.Pointer(&new)))
fmt.Println("after: old =", old, "new =", new)
}
取消注释掉注释行以查看更多详细信息。当然,调用两个单独的atomic.SwapPointer
操作并不是原子操作:有一瞬间,两个映射变量都查看新映射,直到第二次交换使old
变量查看旧映射为止。我认为 unsafe.Pointer
变量p
保留了针对GC的旧映射,直到我们将其存储回old
为止,但对此我一点也不敢肯定(那是Go的黑暗角落之一。
再次:不要这样做。如果测量表明有帮助,请尝试使用sync.Map
。