how to prevent race conditions when read-only a shared struct from an HTTP handler

时间:2018-09-18 19:54:20

标签: go

I need to update values from a struct and return (read-only) not writing from an HTTP handler, to avoid having race conditions I am using sync.Mutex this is a basic example:

http://play.golang.org/p/21IimsdKP6e

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "sync"
    "time"
)

type Counter struct {
    count uint
    flag  bool
    mu    sync.Mutex
    quit  chan struct{}
    time  time.Time
    wg    sync.WaitGroup
}

func (c *Counter) Start() {
    c.count = 1
    c.time = time.Now()
    c.flag = true
}

func (c *Counter) Listen() {
    srv := &http.Server{
        Addr:    ":8080",
        Handler: http.DefaultServeMux,
    }
    http.HandleFunc("/", c.HandleStatus)
    c.wg.Add(1)
    go func() {
        defer c.wg.Done()
        log.Println(srv.ListenAndServe())
    }()
    go func(quit chan struct{}) {
        <-quit
        if err := srv.Close(); err != nil {
            log.Printf("HTTP error: %v", err)
        }
    }(c.quit)
}

func (c *Counter) HandleStatus(w http.ResponseWriter, r *http.Request) {
    c.mu.Lock()
    defer c.mu.Unlock()
    status := struct {
        Count uint   `json:"count"`
        Flag  bool   `json:"flag"`
        Time  string `json:"time"`
    }{
        Count: c.count,
        Time:  c.time.UTC().Format(time.RFC3339),
        Flag:  c.flag,
    }
    w.Header().Set("Content-Type", "application/json")
    if err := json.NewEncoder(w).Encode(status); err != nil {
        log.Println(err)
    }

}

func main() {
    c := &Counter{
        quit: make(chan struct{}),
    }
    c.Start()
    c.Listen()
    timeout := time.After(time.Minute)
    for {
        select {
        case <-time.After(time.Second):
            c.mu.Lock()
            c.count += 1
            c.flag = !c.flag
            c.mu.Unlock()
        case <-timeout:
            close(c.quit)
            c.wg.Wait()
            return
        }
    }
}

I have more handlers but does where I need to read from the Counter struct I need to add on each:

c.mu.Lock()
defer c.mu.Unlock()

And where the variables are modified, do something like:

c.mu.Lock()
c.count += 1
c.flag = !c.flag
c.mu.Unlock()

Therefore wondering how could I better syncronize/arrange the code to prevent adding on each handler Lock/Unlock

1 个答案:

答案 0 :(得分:1)

You have a couple of options:

  1. Use a mutex, as in your existing example (though a RWMutex will be more efficient if most of the accesses are reads).
  2. Wrap the same in a helper function, so that rather than having to operate the lock directly, your handlers can just call the function to get the value, and the function will handle the lock/read/unlock logic.
  3. Turn the logic on its head, letting each routine have its own local copy of the value, and using a channel to notify routines of changes in the authoritative value (sharing by communicating instead of communicating by sharing).