原子交换映射指针导致程序卡死

时间:2019-12-30 03:23:31

标签: go

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),该程序被卡在此处,有人可以提供一些建议。

1 个答案:

答案 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