2012-05-23 8 views
12

W swojej odpowiedzi na to pytanie: Golang for Windows erratic behavior? użytkownik @distributed zalecany do blokowania/synchronizowania dostępu do wspólnej zmiennej na równoczesnych goroutines.Jak zablokować/zsynchronizować dostęp do zmiennej w ruchu podczas równoczesnych goroutinów?

Jak mogę to zrobić?

Szerzej na ten temat:

otrzymuję ten kod (wracającą funkcję z zamknięciem na views) działającym na kilku goroutines w tym samym czasie:

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

to wygląda funkcja IO trwa nadszedł czas, iw efekcie uzyskać ten rodzaj wyjścia: sumujący

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. 

jest w porządku, ale gdy zostanie wydrukowany widzę, że drukowanie operacja + incr ementowanie w ogóle nie jest atomowe.

Gdybym go zmienić na:

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

Wydaje się działać prawidłowo, ale nie jestem całkowicie pewien, czy to nie zawiedzie w końcu ...

+0

Oprócz odpowiedzi wymieniających 'sync/atomic', przycisk [' pakiet expvar'] (https://golang.org/pkg/expvar/) jest kolejnym rozwiązaniem, jeśli również chcę/potrzebuję * publicznie * wystawiać wartości przez HTTP (np. do wysyłania zapytań do wartości na zdalnie uruchomionym serwerze). –

Odpowiedz

19

Jeśli chcesz zsynchronizowanego licznika, wystarczy użyć sync.Mutex do rozwiązania kanonicznego. Pakiet synchronizacji/atomowy powinien być używany tylko w przypadku rzeczy o niskim poziomie lub gdy zmierzyłeś poważny problem z wydajnością.

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

dla danego problemu, polecam zdefiniowania nowego typu, który spełnia interfejs http.Handler, zamiast wracać do zamknięcia. To wygląda zbyt prostsze:

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

"Pakiet synchronizacji/atomowy powinien być używany tylko w przypadku rzeczy o niskim poziomie lub gdy zmierzyłeś poważny problem z wydajnością." Po prostu ciekawy, dlaczego? – ReyCharles

+4

Ponieważ prymitywy, które dostarcza, są łatwe w użyciu - są przeznaczone do mapowania dobrze do instrukcji maszynowych, niekoniecznie do dobrego interfejsu API.W takim przypadku, jeśli osoba przesłuchująca chce uaktualnić, aby przechowywać więcej statystyk niż odsłon strony, kod przy użyciu opcji atomic.AddUint64 nie będzie łatwo zmieniony, aby to uwzględnić - kod używający synchronizacji.Muteks będzie. – rog

10

Pakiet sync ma kilka prymitywów synchronizacji . W zależności od problemu możesz użyć RWMutex lub zwykłego Mutex.

Jeśli chcesz uzyskać bardziej szczegółową odpowiedź, podaj więcej informacji na temat tego, do czego służy.

Edytuj: Po przeczytaniu połączonego pytania prawdopodobnie szukasz sync/atomic, chociaż Mutex też jest w porządku.

Edycja2: Widziałem, jak zaktualizowałeś swój post na przykładzie. Oto kod używający synchronizacji/atomu.

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

(Uwaga: nie testowałem powyższe więc może być literówki/brainfarts) Testowałem go teraz.

+1

To jest lepsza odpowiedź. Atom atomowy użyje raczej instrukcji 'LOCK ADD' niż mutex, co wymaga kilku instrukcji. –

Powiązane problemy