如何从并发地写入映射中恢复?

9

如何从“并发地读写映射”中的运行时恐慌中恢复?通常的defer和recover似乎不起作用。为什么会这样?

我知道在并发上下文中不应该使用映射,但是:在这里如何恢复?

示例:

package main

import "time"

var m = make(map[string]string)

func main() {
    go func() {
        for {
            m["x"] = "foo"
        }
    }()
    go func() {
        for {
            m["x"] = "foo"
        }
    }()

    time.Sleep(1 * time.Second)
}

请添加恢复代码。 :)
2个回答

26

这里无法使用recover来解决问题,因为您遇到的不是一个panic状态。

Go 1.6版本添加了轻量级并发map检测到运行时:

运行时已经添加了轻量级、尽力而为的并发map检测。与往常一样,如果一个goroutine正在写入一个map,其他goroutine就不应该同时读取或写入该map。如果运行时检测到这种情况,它会输出错误信息并强制结束程序。 查找有关该问题更多信息的最佳方法是在race detector竞争检测器下运行程序,它将更可靠地识别数据竞争并提供更多详细信息。

你遇到的是运行时的有意的崩溃,这不是panic()调用的结果,也不能通过一个deferred函数中的recover()调用来停止。

除了防止并发误用maps之外,你无法阻止这种情况的发生。如果您让应用程序保持这种状态而不崩溃,您可能会在运行时遇到神秘的、未定义的行为。


谢谢提供发行说明的参考。我一直在寻找它。 - Igor Lankin
这里的误导在于,堆栈跟踪指向 "/usr/local/go/src/runtime/panic.go:547" 中名为 dopanic 的函数:dopanic(0) - Igor Lankin
@IgorLankin 因为有些事情做得很像恐慌:打印堆栈跟踪,但是应用程序会崩溃。您可以在 panic.go 文件中看到这一点,在 dopanic_m() 函数的末尾:调用了 crash()exit() - icza
为什么Go语言决定在“map”并发写/读时将其设置为不可恢复,而对于“slice”和其他操作(如除以0等)却没有这样做的原因是什么? - undefined
1
@Tate 检测并发地写入映射需要一些最小的开销。由于映射在底层已经是指向结构体的指针,所以可以很容易地添加需要跟踪此操作的额外字段到映射结构中。另一方面,切片不是指针,而是一个小的、由3个字组成的头部,因此添加所需的字段将更加显著。传递映射意味着在底层传递指针。而传递切片意味着复制头部,而不仅仅是一个指针。 - undefined

-1

不要冒险,使用sync包中的互斥锁来保护您的代码。

package main

import (
    "sync"
    "time"
)

var m = make(map[string]string)
var l = sync.Mutex{}

func main() {
    go func() {
        for {
            l.Lock()
            m["x"] = "foo"
            l.Unlock()
        }
    }()
    go func() {
        for {
            l.Lock()
            m["x"] = "foo"
            l.Unlock()
        }
    }()

    time.Sleep(1 * time.Second)
}

6
这不是问题所在。问题是为什么恢复功能无法正常工作。 - Igor Lankin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接