How to lock/synchronize access to a variable in Go during concurrent goroutines?

Sebastián Grignoli picture Sebastián Grignoli · May 24, 2012 · Viewed 17.3k times · Source

In his answer to this question: Golang for Windows erratic behavior? user @distributed recommended to lock/synchronize access to a shared variable on concurrent goroutines.

How can I do that?

More on the issue:

I get this code (the returned function with a closure on views) running on several goroutines at the same time:

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

It looks like the IO function takes it's time, and as a result I get this kind of output:

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.

It increments fine, but when it gets printed I can see that the operation printing+incrementing is not atomic at all.

If I change it to:

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

It seems to work fine, but I'm not completely sure if it will not fail eventually...

Answer

rog picture rog · May 24, 2012

If a synchronized counter is all you want, then using sync.Mutex is the canonical solution. The sync/atomic package should only be used for low level stuff or when you've measured a serious performance problem.

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

For your particular problem, I'd suggest defining a new type that satisfies the http.Handler interface, rather than returning a closure. That looks simpler too:

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