2012-05-23 10 views
12

En su respuesta a esta pregunta: Golang for Windows erratic behavior? user @distributed se recomienda para bloquear/sincronizar el acceso a una variable compartida en goroutines concurrentes.Cómo bloquear/sincronizar el acceso a una variable en Go durante las rutinas concurrentes?

¿Cómo puedo hacer eso?

Más sobre el tema:

puedo obtener el código (la función devuelta con un cierre en views) que se ejecuta en varios goroutines al mismo tiempo:

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

Parece que la función IO toma es el momento, y como resultado me da este tipo de salida:

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. 

incrementa bien, pero cuando se imprime puedo ver que la operación de impresión + incr la aplicación no es atómica en absoluto.

Si lo cambio a:

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

Parece que funciona bien, pero no estoy completamente seguro si no va a fallar con el tiempo ...

+0

Además de las respuestas mencionan 'sincronización/atomic', el [' paquete expvar'] (https://golang.org/pkg/expvar/) es otra opción si también quiere/necesita mostrar * públicamente * los valores a través de HTTP (por ejemplo, para consultar los valores en un servidor que se ejecuta de forma remota). –

Respuesta

19

Si un contador sincronizado es todo lo que desea, usar sync.Mutex es la solución canónica. El paquete de sincronización/atómico solo se debe usar para cosas de bajo nivel o cuando haya medido un problema grave de rendimiento.

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

para su problema particular, me gustaría sugerir la definición de un nuevo tipo que satisface la interfaz http.Handler, en lugar de devolver un cierre. Esto se ve muy simple también:

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

"El paquete de sincronización/atómico solo debe usarse para cosas de bajo nivel o cuando haya medido un problema grave de rendimiento". Solo curiosidad, ¿por qué es eso? – ReyCharles

+4

Porque las primitivas que proporciona son fáciles de usar, están diseñadas para correlacionarse bien con las instrucciones de la máquina, no necesariamente con una buena API.En este caso, si el interlocutor desea actualizar para almacenar más estadísticas que no sean vistas de página, el código que usa atomic.AddUint64 no se cambiará fácilmente para acomodar esto: el código que usa sync.Mutex lo hará. – rog

10

El paquete sync tiene algunas primitivas de sincronización . Dependiendo del problema, puede usar un RWMutex o un Mutex normal.

Si quiere una respuesta más específica, por favor brinde más información sobre para qué sirve.

Editar: Después de leer la pregunta vinculada probablemente estés buscando sync/atomic, aunque un Mutex también está bien.

Edit2: Vi que actualizaste tu publicación con un ejemplo. Aquí está el código usando sincronización/atómico.

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

(Nota: No he probado las lo que puede haber errores tipográficos/brainfarts anteriores) he comprobado ahora.

+1

Esta es la mejor respuesta. Un incremento atómico utilizará una instrucción 'LOCK ADD' en lugar de un mutex, que requiere varias instrucciones. –

Cuestiones relacionadas