Ir a las preguntas de la entrevista: principio de implementación de bloqueo sync-mutex

En Go, se implementan dos tipos principales de bloqueos: sync.Mutex (bloqueo mutex) y sync.RWMutex (bloqueo de lectura-escritura).

Este artículo presenta principalmente los principios de uso e implementación de sync.Mutex.

por qué se necesita bloqueo

En condiciones de alta concurrencia o cuando se ejecutan varias rutinas al mismo tiempo, la misma memoria se puede leer y escribir al mismo tiempo, como en el siguiente escenario:

var count int
var mu sync.Mutex

func func1() {
    
    
	for i := 0; i < 1000; i++ {
    
    
		go func() {
    
    
			count = count + 1
		}()
	}
	time.Sleep(time.Second)
	fmt.Println(count)
}

Se espera que el valor de salida sea 1000, pero el valor real es 948, 965, etc. Los resultados de múltiples ejecuciones son inconsistentes.
La razón por la que ocurre este fenómeno es porque para count=count+1, los pasos de ejecución de cada gorutina son:

  • Leer el valor de conteo actual
  • contar+1
  • Modificar valor de conteo

Cuando se ejecutan varias rutinas para modificar el valor al mismo tiempo, la rutina ejecutada más tarde sobrescribirá la modificación del recuento realizada por la rutina anterior.

En Go, el método más utilizado para restringir el acceso a recursos públicos por parte de programas concurrentes es el bloqueo mutex (sync.mutex).

Hay dos métodos comunes de sync.mutex:

  • Mutex.lock() se utiliza para obtener el bloqueo.
  • Mutex.Unlock() se utiliza para liberar el bloqueo.
    La sección de código entre los métodos Lock y Unlock se denomina sección crítica del recurso. El código de esta sección está estrictamente protegido por bloqueos y es seguro para subprocesos. En cualquier momento, a lo sumo hay una gorutina ejecutándose.

En base a esto, el ejemplo anterior se puede mejorar usando sync.mutex:

var count int
var mutex sync.Mutex

func func2() {
    
    
	for i := 0; i < 1000; i++ {
    
    
		go func() {
    
    
			mutex.Lock()
			count = count + 1
			mutex.Unlock()
		}()
	}
	time.Sleep(time.Second)
	fmt.Println(count)
}

El resultado de salida es 1000.

Cuando una gorutina ejecuta el método mutex.lock (), si otras gorutinas realizan operaciones de bloqueo, serán bloqueadas. Otras gorutinas no continuarán agarrando el bloqueo hasta que la gorutina actual ejecute el método mutex.unlock () para liberar el bloqueo. ejecución de bloqueo.

Principio de implementación

Estructura de datos de sync.Mutex
La estructura de sync.Mutex en Go es:

type Mutex struct {
    
    
	state int32
	sema  uint32
}

Sync.Mutex consta de dos campos, el estado se usa para indicar el estado actual del bloqueo mutex y sema se usa para controlar el semáforo del estado del bloqueo. Creo que después de leer las descripciones de estos dos campos, todos los taoístas pueden parecer entenderlos, pero puede que no sea así. Entendamos en detalle qué hacen estos dos campos.
El estado de bloqueo mutex registra principalmente los siguientes cuatro estados:

waiter_num : registra el número de gorutinas actualmente esperando para tomar este candado.
starving : si el candado actual está en un estado de inanición (el estado de inanición del desbloqueo se detallará más adelante) 0: estado normal 1: estado de inanición despertado
: si una gorutina en el bloqueo actual ha sido despertado. 0: no se despierta ninguna rutina; 1: hay una rutina en el proceso de bloqueo.
Bloqueado : si la rutina mantiene el bloqueo actual. 0: No retenido 1: Ya retenido
El papel del semáforo sema :
Cuando la gorouine que sostiene el candado libera el candado, se liberará el semáforo sema. Este semáforo despertará al gorouine que estaba previamente bloqueado agarrando el candado para adquirir el cerrar con llave.

Dos modos de bloqueo

Las cerraduras Mutex están diseñadas en dos modos principales: modo normal y modo de inanición.

La razón por la que se introduce el modo de inanición es para garantizar la equidad en la adquisición rutinaria de bloqueos mutex. La llamada equidad en realidad significa que cuando varias gorutinas adquieren bloqueos, es justo si el orden en que las gorutinas adquieren los bloqueos es coherente con el orden en que se solicitan los bloqueos.
En el modo normal, todas las gorutinas bloqueadas en la cola de espera adquirirán bloqueos en orden. Cuando se despierta una gorutina en la cola de espera, esta gorutina no adquirirá directamente el bloqueo, sino que competirá con la nueva gorutina que solicita el bloqueo. Por lo general, es más fácil para una gorutina que solicita recientemente un bloqueo adquirir el bloqueo, esto se debe a que la gorutina que solicita recientemente un bloqueo ocupa el segmento de CPU para su ejecución y existe una alta probabilidad de que la lógica para adquirir el bloqueo pueda ser ejecutado directamente.

En modo de inanición , la gorutina que solicita recientemente un bloqueo no adquirirá el bloqueo, sino que se unirá al final de la cola y bloqueará en espera de adquirir el bloqueo.
Condiciones de activación para el modo de hambre :

  • Cuando una gorutina espera el bloqueo durante más de 1 ms, el bloqueo mutex cambiará al modo de inanición.

Condiciones para cancelar el modo hambre:

  • Cuando la rutina que adquiere el bloqueo es la última rutina en la cola que espera el bloqueo, el bloqueo mutex cambiará al modo normal.
  • Cuando el tiempo de espera de la gorutina que adquiere el bloqueo es de 1 ms, el bloqueo mutex cambiará al modo normal.

Precauciones

  1. Después de ejecutar Lock () con éxito en una rutina, no vuelva a bloquear, de lo contrario entrará en pánico.
  2. Al ejecutar Unlock() antes de Lock() para liberar el bloqueo, entrará en pánico.
  3. Para el mismo bloqueo, puede ejecutar Lock en una rutina y, después de bloquearlo con éxito, puede ejecutar Desbloquear en otra rutina para liberar el bloqueo.

Supongo que te gusta

Origin blog.csdn.net/m0_73728511/article/details/133011077
Recomendado
Clasificación