Perguntas da entrevista: princípio de implementação de bloqueio sincronizado-mutex

No Go, dois tipos principais de bloqueios são implementados: sync.Mutex (bloqueio mutex) e sync.RWMutex (bloqueio de leitura-gravação).

Este artigo apresenta principalmente os princípios de uso e implementação do sync.Mutex.

por que o bloqueio é necessário

Sob alta simultaneidade ou quando várias goroutines são executadas ao mesmo tempo, a mesma memória pode ser lida e gravada ao mesmo tempo, como no seguinte cenário:

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

Espera-se que o valor de saída seja 1000, mas o valor real é 948, 965, etc. Os resultados de múltiplas execuções são inconsistentes.
A razão pela qual esse fenômeno ocorre é porque para count=count+1, as etapas de execução de cada goroutine são:

  • Leia o valor da contagem atual
  • contar+1
  • Modificar valor de contagem

Quando várias goroutines são executadas para modificar o valor ao mesmo tempo, a goroutine executada posteriormente substituirá a modificação da contagem pela goroutine anterior.

No Go, o método mais comumente usado para restringir o acesso a recursos públicos por programas simultâneos é o bloqueio mutex (sync.mutex).

Existem dois métodos comuns de sync.mutex:

  • Mutex.lock() é usado para obter o bloqueio
  • Mutex.Unlock() é usado para liberar o bloqueio.
    A seção de código entre os métodos Lock e Unlock é chamada de seção crítica do recurso. O código nesta seção é estritamente protegido por bloqueios e é seguro para threads. A qualquer momento, no máximo, há uma goroutine em execução.

Com base nisso, o exemplo acima pode ser melhorado 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)
}

O resultado de saída é 1000.

Quando uma goroutine executa o método mutex.lock(), se outras goroutines realizarem operações de bloqueio, elas serão bloqueadas.Outras goroutines não continuarão a capturar o bloqueio até que a goroutine atual execute o método mutex.unlock() para liberar o bloqueio. execução de bloqueio.

Princípio de implementação

Estrutura de dados de sincronizar.Mutex
A estrutura de sincronizar.Mutex em Go é:

type Mutex struct {
    
    
	state int32
	sema  uint32
}

Sync.Mutex consiste em dois campos, state é usado para indicar o estado atual do bloqueio mutex e sema é usado para controlar o semáforo do status do bloqueio. Acredito que depois de ler as descrições desses dois campos, todos os taoístas podem parecer entendê-los, mas talvez não. Vamos entender em detalhes o que esses dois campos fazem.
O estado de bloqueio mutex registra principalmente os quatro estados a seguir:

waiter_num : registra o número de goroutines atualmente esperando para capturar este bloqueio.
starving : se o bloqueio atual está em estado de inanição (o estado de inanição de desbloqueio será detalhado mais tarde) 0: estado normal 1: estado de inanição acordado
: se uma goroutine está em o bloqueio atual foi ativado. 0: Nenhuma goroutine foi despertada; 1: Há uma goroutine em processo de bloqueio.
Locked : Se o bloqueio atual é mantido pela goroutine. 0: Não mantido 1: Já mantido
O papel do semáforo sema :
Quando o gorouine que está segurando o bloqueio libera o bloqueio, o semáforo sema será liberado. Este semáforo irá acordar o gorouine que foi bloqueado anteriormente, agarrando o bloqueio para adquirir o trancar.

Dois modos de bloqueio

Os bloqueios Mutex são projetados em dois modos principais: modo normal e modo de fome.

A razão pela qual o modo de fome é introduzido é para garantir a imparcialidade da aquisição goroutina de bloqueios mutex. A chamada justiça, na verdade, significa que quando várias goroutines adquirem bloqueios, é justo se a ordem em que as goroutines adquirem os bloqueios for consistente com a ordem em que os bloqueios são solicitados.
No modo normal, todas as goroutines bloqueadas na fila de espera adquirirão bloqueios em ordem.Quando uma goroutine na fila de espera for despertada, esta goroutine não adquirirá diretamente o bloqueio, mas competirá com a nova goroutine que solicita o bloqueio. Normalmente,é mais fácil para uma goroutine que solicita recentemente um bloqueio adquirir o bloqueio.Isto ocorre porque a goroutine que solicita recentemente um bloqueio está ocupando a fatia da CPU para execução, e há uma grande probabilidade de que a lógica para adquirir o bloqueio possa ser executado diretamente.

No modo de fome , a goroutine que solicitar um bloqueio recentemente não irá adquiri-lo, mas entrará no final da fila e bloqueará a espera para adquiri-lo.
Condições de gatilho para o modo fome :

  • Quando uma goroutine espera pelo bloqueio por mais de 1 ms, o bloqueio mutex mudará para o modo de fome.

Condições para cancelar o modo fome:

  • Quando a goroutine que adquire o bloqueio for a última goroutine na fila aguardando o bloqueio, o bloqueio mutex mudará para o modo normal.
  • Quando o tempo de espera da goroutine que adquire o bloqueio estiver dentro de 1 ms, o bloqueio mutex mudará para o modo normal.

Precauções

  1. Depois de executar Lock() com sucesso em uma goroutine, não bloqueie novamente, caso contrário, entrará em pânico.
  2. Executar Unlock() antes de Lock() para liberar o bloqueio entrará em pânico.
  3. Para o mesmo bloqueio, você pode executar Lock em uma goroutine. Após o bloqueio bem-sucedido, você pode executar Unlock em outra goroutine para liberar o bloqueio.

Acho que você gosta

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