Ir a notas de combate de idiomas (13) | Ir a competencia de recursos simultáneos

Si hay concurrencia, habrá competencia de recursos. Si dos o más gorutinas acceden a un recurso compartido sin estar sincronizadas entre sí, como leer y escribir en el recurso al mismo tiempo, estarán en un estado de competencia. es la competencia de recursos en concurrencia.

La concurrencia en sí no es complicada, pero debido al problema de la competencia de recursos, se ha vuelto más complicado para nosotros desarrollar buenos programas concurrentes, porque causará muchos problemas inexplicables.

package main

import (
	"fmt"
	"runtime"
	"sync"
)

var (
	count int32
	wg    sync.WaitGroup
)

func main() {
	wg.Add(2)
	go incCount()
	go incCount()
	wg.Wait()
	fmt.Println(count)
}

func incCount() {
	defer wg.Done()
	for i := 0; i < 2; i++ {
		value := count
		runtime.Gosched()
		value++
		count = value
	}
}

   

Este es un ejemplo de competencia de recursos. Podemos ejecutar este programa varias veces y encontrar que el resultado puede ser 2, 3 o 4. Debido a que la countvariable de recurso compartido no tiene ninguna protección de sincronización, ambas goroutines las leerán y escribirán, lo que hará que el resultado calculado se sobrescriba y produzca resultados incorrectos. Aquí demostramos una posibilidad. Llamaremos temporalmente a las dos goroutines Para g1 y g2.

  1. g1 lee que el recuento es 0.
  2. Luego, g1 hace una pausa, cambia a g2 para ejecutarse y g2 lee que el conteo también es 0.
  3. g2 hace una pausa, cambia a g1, los pares de g1 cuentan + 1 y el conteo se convierte en 1.
  4. G1 hace una pausa y cambia a g2. G2 acaba de obtener el valor 0, y es +1. Finalmente, el valor asignado a contar sigue siendo 1.
  5. ¿Ha notado que el resultado de g1 en la cuenta + 1 fue sobrescrito por g2, y ambas gorutinas son +1 o 1?

No continuaré más con la demostración, el resultado aquí es incorrecto y las dos gorutinas sobrescriben el resultado. Lo que estamos aquí runtime.Gosched()es dejar que la goroutine actual se detenga, volver a la cola de ejecución y dejar que se ejecuten otras goroutines en espera. El propósito es dejarnos demostrar que el resultado de la competencia de recursos es más obvio. Tenga en cuenta que los problemas de CPU también estarán involucrados aquí, y varios núcleos serán paralelos, por lo que el efecto de la competencia de recursos es más obvio.

Por lo tanto, nuestra lectura y escritura del mismo recurso debe ser atómica, es decir, solo una goroutine puede leer y escribir recursos compartidos a la vez .

 

El problema de la competencia por los recursos compartidos es muy complejo y difícil de detectar, afortunadamente Go nos proporciona una herramienta que nos ayuda a verificar, este es un go build -racecomando. Ejecutamos este comando en el directorio del proyecto actual para generar un archivo ejecutable y luego ejecutamos el archivo ejecutable para ver la información de detección impresa.

go build -race
   

Se agrega una -racebandera adicional para que el programa ejecutable generado tenga su propia función de detectar competencia de recursos, y lo ejecutaremos a continuación, ejecutándose también en la terminal.

./hello
   

El nombre del archivo ejecutable generado por mi ejemplo aquí es hello, por lo que se ejecuta así. En este momento, veamos el resultado de detección generado por el terminal.

➜  hello ./hello       
==================
WARNING: DATA RACE
Read at 0x0000011a5118 by goroutine 7:
  main.incCount()
      /Users/xxx/code/go/src/flysnow.org/hello/main.go:25 +0x76

Previous write at 0x0000011a5118 by goroutine 6:
  main.incCount()
      /Users/xxx/code/go/src/flysnow.org/hello/main.go:28 +0x9a

Goroutine 7 (running) created at:
  main.main()
      /Users/xxx/code/go/src/flysnow.org/hello/main.go:17 +0x77

Goroutine 6 (finished) created at:
  main.main()
      /Users/xxx/code/go/src/flysnow.org/hello/main.go:16 +0x5f
==================
4
Found 1 data race(s)

   

Mira, encuentra una competencia de recursos, e incluso el problema en esa línea de código está marcado. Goroutine 7 lee el recurso compartido en la línea 25 de código value := county goroutine 6 está modificando el recurso compartido en la línea 28 de código count = value, y ambas goroutines se inician desde la función principal, en las líneas 16, 17 a través de gopalabras clave.

Dado que ya conocemos el problema de la competencia de recursos compartidos, se debe a que dos o más gorutinas leen y escriben al mismo tiempo, por lo que solo debemos asegurarnos de que solo una goroutine pueda leer y escribir al mismo tiempo. analizará la solución tradicional.Método de competencia de recursos-bloqueo de recursos.

Go language proporciona algunas funciones en el paquete atómico y el paquete de sincronización para sincronizar los recursos compartidos. Veamos primero el paquete atómico.

package main

import (
	"fmt"
	"runtime"
	"sync"
	"sync/atomic"
)

var (
	count int32
	wg    sync.WaitGroup
)

func main() {
	wg.Add(2)
	go incCount()
	go incCount()
	wg.Wait()
	fmt.Println(count)
}

func incCount() {
	defer wg.Done()
	for i := 0; i < 2; i++ {
		value := atomic.LoadInt32(&count)
		runtime.Gosched()
		value++
		atomic.StoreInt32(&count,value)
	}
}

   

Preste atención a aquí atomic.LoadInt32y a atomic.StoreInt32dos funciones, una es leer el valor de la variable de tipo int32 y la otra es modificar el valor de la variable de tipo int32. Ambas son operaciones atómicas. Go nos ha ayudado a usar el mecanismo de bloqueo en la parte inferior para Garantizar recursos compartidos Sincronización y seguridad para que podamos obtener los resultados correctos En este momento, usaremos la herramienta de detección de competencia de recursos para go build -raceverificar, y no habrá problemas.

Hay muchas funciones atómicas en el paquete atómico para garantizar el acceso y la modificación simultánea de sincronización de recursos. Por ejemplo, una función atomic.AddInt32puede modificar directamente una variable de tipo int32. La cantidad de funciones más que se agregan al valor original también es atómica., No hay más ejemplos aquí , puede probarlo usted mismo.

 

Aunque atomic puede resolver el problema de la competencia de recursos, la comparación es relativamente simple y los tipos de datos admitidos también son limitados. Por lo tanto, el lenguaje Go también proporciona un paquete de sincronización. Este paquete de sincronización proporciona un bloqueo mutuamente excluyente que nos permite controla a qué códigos y solo una goroutine puede acceder al mismo tiempo. El rango de código controlado por el sync mutex se denomina sección crítica. El código de la sección crítica solo puede ser accedido por otra goroutine al mismo tiempo. En el ejemplo de ahora, todavía podemos hacer esta transformación.

package main

import (
	"fmt"
	"runtime"
	"sync"
)

var (
	count int32
	wg    sync.WaitGroup
	mutex sync.Mutex
)

func main() {
	wg.Add(2)
	go incCount()
	go incCount()
	wg.Wait()
	fmt.Println(count)
}

func incCount() {
	defer wg.Done()
	for i := 0; i < 2; i++ {
		mutex.Lock()
		value := count
		runtime.Gosched()
		value++
		count = value
		mutex.Unlock()
	}
}
   

En el ejemplo, un mutex se declara recientemente mutex sync.Mutex. Hay dos métodos para este mutex, uno es mutex.Lock()y el otro es mutex.Unlock(). El área entre los dos es el área crítica y el código en el área crítica es seguro.

En el ejemplo, primero llamamos mutex.Lock()para bloquear el código con recursos de la competencia, de modo que cuando una goroutine ingrese a esta área, otras gorutinas no pueden ingresar y solo pueden esperar hasta que se mutex.Unlock()libere la llamada para liberar el bloqueo.

 

Este método es más flexible y permite que el escritor de código defina arbitrariamente el rango de código que debe protegerse, es decir, la sección crítica. Además de funciones atómicas y bloqueos mutex, Go también nos proporciona funciones que son más fáciles de sincronizar en múltiples goroutines, este es el canal chan, del que hablaremos en el próximo artículo.

Supongo que te gusta

Origin blog.csdn.net/qq_32907195/article/details/112398676
Recomendado
Clasificación