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

时间:2012-05-23 22:41:14

标签: go goroutine

在回答这个问题时:Golang for Windows erratic behavior? user @distributed建议锁定/同步对并发goroutines上的共享变量的访问。

我该怎么做?

有关此问题的更多信息:

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

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.

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

如果我将其更改为:

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)
    }
}

它似乎工作正常,但我不完全确定它最终是否会失败......

2 个答案:

答案 0 :(得分:23)

如果您想要同步计数器,则使用sync.Mutex是规范解决方案。同步/原子包只应用于低级别的东西,或者当你测量到严重的性能问题时。

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))
}

答案 1 :(得分:10)

sync包有一些同步原语。根据问题,您可以使用RWMutex或普通互斥锁。

如果您想要更具体的答案,请提供更多信息,了解它的用途。

编辑:阅读链接的问题后,您可能正在寻找sync/atomic,但互联网也很好。

Edit2:我看到你用一个例子更新了你的帖子。这是使用sync / atomic的代码。

func makeHomeHandler() func(w http.ResponseWriter, r *http.Request) {
    var views *uint64 = new(uint64)
    atomic.StoreUint64(views, 0) // I don't think this is strictly necessary
    return func(w http.ResponseWriter, r *http.Request) {
        // Atomically add one to views and get the new value
        // Perhaps you want to subtract one here
        views_now := atomic.AddUint64(views, 1) 
        fmt.Fprintf(w, "Counting %s, %d so far.", r.URL.Path[1:], views_now)
    }
}

(注意:我没有测试过以上所以可能存在拼写错误/脑筋事件)我现在测试了它。