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

StackOverflow https://stackoverflow.com/questions/10728863

  •  10-06-2021
  •  | 
  •  

質問

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...

役に立ちましたか?

解決

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

他のヒント

The sync package has some synchronization primitives. Depending on the problem you could use a RWMutex or a plain Mutex.

If you want a more specific answer please give some more information on what it's for.

Edit: After reading the linked question you're probably looking for sync/atomic, though a Mutex is fine too.

Edit2: I saw you updated your post with an example. Here's the code using 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)
    }
}

(Note: I have not tested the above so there might be typos/brainfarts) I tested it now.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top