【问题标题】:Going mutex-less减少互斥量
【发布时间】:2015-09-26 09:46:11
【问题描述】:

好吧,去“专家”吧。你将如何用惯用的 Go 编写这段代码,也就是在 next 中没有互斥锁?

package main

import (
    "fmt"
)

func main() {
    done := make(chan int)
    x := 0
    for i := 0; i < 10; i++ {
        go func() {
            y := next(&x)
            fmt.Println(y)
            done <- 0
        }()
    }
    for i := 0; i < 10; i++ {
        <-done
    }
    fmt.Println(x)

}

var mutex = make(chan int, 1)

func next(p *int) int {
    mutex <- 0
    // critical section BEGIN
    x := *p
    *p++
    // critical section END
    <-mutex
    return x

}

假设你不能同时在临界区有两个 goroutine,否则会发生不好的事情。

我的第一个猜测是有一个单独的 goroutine 来处理状态,但我无法找到匹配输入/输出的方法。

【问题讨论】:

  • sync/atomic使用atomic.Value?

标签: go concurrency


【解决方案1】:

您将使用实际的 sync.Mutex:

var mutex sync.Mutex

func next(p *int) int {
    mutex.Lock()
    defer mutex.Unlock()
    x := *p
    *p++
    return x
}

尽管您可能还会将next 功能、状态和sync.Mutex 组合到一个结构中。

虽然在这种情况下没有理由这样做,但由于 Mutex 更适合围绕单个资源进行互斥,您可以使用 goroutine 和通道来实现相同的效果

http://play.golang.org/p/RR4TQXf2ct

x := 0

var wg sync.WaitGroup
send := make(chan *int)
recv := make(chan int)

go func() {
    for i := range send {
        x := *i
        *i++
        recv <- x
    }
}()

for i := 0; i < 10; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        send <- &x
        fmt.Println(<-recv)
    }()
}
wg.Wait()
fmt.Println(x)

【讨论】:

  • 您对“不使用互斥锁”的回答是“使用互斥锁”?对不起,这不是正确的答案。我知道我可以这样做,但我正在寻找“正确”的方式,也就是使用频道传递消息。
  • @BrianMalehorn:这是正确的方法,也是最常见的。您询问了“go 专家”的建议,这就是我们的做法(首先,检查该模式在整个 std 库中的使用频率)。
  • @BrianMalehorn:添加了一个用于同步访问的带有通道和 goroutine 的示例
  • 嗯,如果你说它是正确的方法,我会使用互斥锁。但是,上面的代码有竞争。线程 1 执行 send &lt;- p,线程 2 执行 send &lt;- q,线程 2 执行 &lt;-recv,线程 1 执行 &lt;-recv。现在他们得到了对方的回应。如果您稍微更改上面的代码,这将是一个问题:play.golang.org/p/ruNsFSgJau
  • 没关系,我以为通道被缓冲了。上面的代码是正确的。
【解决方案2】:

正如@favoretti 提到的,同步/原子是一种实现方式。

但是,您必须使用 int32 或 int64 而不是 int(因为 int 在不同平台上的大小可能不同)。

这是Playground上的一个例子

package main

import (
    "fmt"
    "sync/atomic"
)

func main() {
    done := make(chan int)
    x := int64(0)
    for i := 0; i < 10; i++ {
        go func() {
            y := next(&x)
            fmt.Println(y)
            done <- 0
        }()
    }
    for i := 0; i < 10; i++ {
        <-done
    }
    fmt.Println(x)

}

func next(p *int64) int64 {
    return atomic.AddInt64(p, 1) - 1
}

【讨论】:

  • 好吧,我不是这个意思。如果是*p = (*p + 1) * 5,你会怎么做?或者实际上任何没有atomic 版本的东西。
  • 如前所述,我会在您的示例中使用 sync.Mutex 。通道的结构太重,不能用作纯互斥体。这在一定程度上取决于关键部分的复杂性来做什么,但总的来说,除非我可以异步处理其他操作,否则我不会使用通道。
猜你喜欢
  • 2016-09-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-01
  • 2022-01-14
  • 2011-04-20
  • 2011-01-06
相关资源
最近更新 更多