一尘不染

在并发goroutine期间如何锁定/同步Go中对变量的访问?

go

在他对以下问题的回答中:Windows的Golang行为不稳定?建议用户@distributed锁定/同步并发goroutine上对共享变量的访问。

我怎样才能做到这一点?

有关此问题的更多信息:

我获得views了同时在多个goroutines 上运行的代码(带有闭包的返回函数):

func makeHomeHandler() func(c *http.Conn, r *http.Request) {
    views := 1
    return func(c *http.Conn, r *http.Request) {
        fmt.Fprintf(c, "Counting %s, %d so far.", r.URL.Path[1:], views)
        views++
    }
}

看起来IO功能很费时间,结果我得到了这种输出:

Counting monkeys, 5 so far.
Counting monkeys, 5 so far.
Counting monkeys, 5 so far.
Counting monkeys, 8 so far.
Counting monkeys, 8 so far.
Counting monkeys, 8 so far.
Counting monkeys, 11 so far.

它可以很好地递增,但是当它被打印时,我可以看到print + incrementing操作根本不是原子的。

如果我将其更改为:

func makeHomeHandler() func(c *http.Conn, r *http.Request) {
    views := 0
    return func(c *http.Conn, r *http.Request) {
        views++
        // I can only hope that other goroutine does not increment the counter 
        // at this point, i.e., right after the previous line and before the 
        // next one are executed!
        views_now := views
        fmt.Fprintf(c, "Counting %s, %d so far.", r.URL.Path[1:], views_now)
    }
}

看来工作正常,但我不确定是否最终不会失败…


阅读 224

收藏
2020-07-02

共1个答案

一尘不染

如果只需要一个同步计数器,那么使用sync.Mutex就是规范的解决方案。sync / atomic程序包仅应用于低级内容或测量到严重的性能问题时使用。

type Counter struct {
    mu  sync.Mutex
    x   int64
}

func (c *Counter) Add(x int64) {
    c.mu.Lock()
    c.x += x
    c.mu.Unlock()
}

func (c *Counter) Value() (x int64) {
    c.mu.Lock()
    x = c.x
    c.mu.Unlock()
    return
}

func makeHomeHandler() func(c http.ResponseWriter, r *http.Request) {
    var views Counter
    return func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Counting %s, %d so far.", r.URL.Path[1:], views.Value())
        views.Add(1)
    }
}

对于您的特定问题,建议您定义一个满足http.Handler接口的新类型,而不是返回闭包。这看起来也更简单:

type homeHandler struct {
    mu  sync.Mutex
    views   int64
}

func (h *homeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    h.mu.Lock()
    defer h.mu.Unlock()
    fmt.Fprintf(w, "Counting %s, %d so far.", r.URL.Path[1:], h.views)
    h.views++
}

func init() {
    http.Handle("/", new(homeHandler))
}
2020-07-02