【问题标题】:Data race when GOMAXPROCS=1GOMAXPROCS=1 时的数据竞争
【发布时间】:2013-10-31 07:32:08
【问题描述】:

我正在尝试理解 Golang typical data races 之一,其中从多个 goroutine 访问未受保护的全局变量可能会导致竞争条件:

var service map[string]net.Addr

func RegisterService(name string, addr net.Addr) {
  service[name] = addr
}

func LookupService(name string) net.Addr {
  return service[name]
}

接着说我们可以通过使用互斥锁来保护它来解决这个问题:

var (
  service   map[string]net.Addr
  serviceMu sync.Mutex
)

func RegisterService(name string, addr net.Addr) {
  serviceMu.Lock()
  defer serviceMu.Unlock()
  service[name] = addr
}

func LookupService(name string) net.Addr {
  serviceMu.Lock()
  defer serviceMu.Unlock()
  return service[name]
}

到目前为止,一切都很好。让我困惑的是:

this question 的公认答案表明,受 CPU 限制的 goroutine 将使任何已被多路复用到同一 OS 线程上的其他 goroutine 饿死(除非我们明确地使用 runtime.Gosched() 让步)。这是有道理的。

对我来说,上面的 RegisterService()LookupService() 函数看起来受 CPU 限制,因为没有 IO 也没有产量。这是正确的吗?

如果是,并且如果 GOMAXPROCS 设置为 1,那么上面示例中的互斥锁仍然是绝对必要的吗? goroutine 在可能发生竞态条件的地方受 CPU 限制这一事实是否解决了这个问题?

即使是这样,我认为在现实生活中使用互斥锁仍然是一个好主意,因为我们可能无法保证 GOMAXPROCS 的设置。还有其他原因吗?

【问题讨论】:

  • Go 例程受 CPU 限制的事实并不意味着它们之间没有机会发生调度。互斥体仍然是必需的。
  • 谢谢。因此,受 CPU 限制的 goroutine 可能会饿死同一 OS 线程上的其他 goroutine,也可能不会。无论哪种方式,我们都无法保证。如果是这样,你知道是什么因素决定了一个 goroutines 是否会饿死其他人吗?
  • 一个 CPU Bound goroutine 用于在同一个线程上饿死其他人,但 Go 1.2(即将发布)有一个抢占式调度程序,因此不再是这种情况。恕我直言,这只是一个实现细节。 (在 go 1.2 中,每个函数调用都是抢占的机会,因此如果您的 CPU 绑定函数不调用其他函数,它将使同一线程上的其他函数挨饿)。
  • @AlexEdwards 如果您的代码对调度程序的行为做出任何假设,它就会利用未定义的行为。不要写这样的代码。假设调度程序可能会在代码中的每个点在 Goroutine 之间切换。
  • @NickCraig-Wood 你可能会发布它作为答案。

标签: go race-condition


【解决方案1】:

正如 FUZxxl 和 Nick Craig-Wood 所指出的,goroutines 的当前行为是特定于实现的。所以,也许,读或写地图可以产生。考虑到maps are not thread safe,正确的并发访问需要互斥锁或其他同步。

作为互斥锁的替代方案,您可以生成一个 goroutine,它在您的地图上执行所有操作并通过通道与其对话:

type writereq struct {
    key string
    value net.Addr
    reply chan struct{}
}

type readreq struct {
    key string
    reply chan net.Addr
}

var service map[string]net.Addr
var reads = make(chan readreq)
var writes = make(chan writereq)

func RegisterService(name string, addr net.Addr) {
    w := writereq{name, addr, make(chan struct{})}
    writes <- w
    return <-w.reply // return after registration confirmation
}

func LookupService(name string) net.Addr {
    r := readreq{name, make(chan net.Addr)}
    reads <- r
    return <-r.reply
}

func serveRegistry() {
    for {
        select {
        case r := <-reads:
            r.reply <- service[r.name]
        case w := <-writes:
            service[w.name] = w.addr
            w.reply <- struct{}
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-04-08
    • 1970-01-01
    • 2022-01-04
    • 1970-01-01
    • 2015-12-27
    • 2019-03-19
    • 1970-01-01
    相关资源
    最近更新 更多