Quando dois ou mais Threads/Processos competem por um recurso.
Race Condition (Condição de Corrida)
Iremos utilizar goroutines que é a forma de executar uma atividade de maneira concorrente em go.
Problema:
Suponha que temos três clientes: um chamado A, outro chamado B e outro C. Todos estão interessados em comprar a mesma camisa em um e-commerce, porém existem apenas 10 unidades disponíveis dessa camisa. Crie uma goroutine para cada cliente e simule a compra dessa camisa, "printando" na tela qual cliente comprou a camisa, a quantidade comprada e qual a quantidade restante de camisas no estoque.
package main
import (
"fmt"
"strconv"
"sync"
"time"
)
type itemType struct {
itemId int
name string
price int
unit int
}
func (i *itemType) takeItem(customer *customerType, unit int) {
// Simula um atraso para aumentar a chance de condição de corrida
time.Sleep(50 * time.Millisecond)
if i.unit > 0 && i.unit >= unit {
i.unit = i.unit - unit
fmt.Println("O Cliente " + customer.name + " comprou " + strconv.Itoa(unit) + " unidade(s) de " + i.name + " e a quantidade restante no estoque é " + strconv.Itoa(i.unit))
} else {
fmt.Println("O Cliente " + customer.name + " não conseguiu comprar " + strconv.Itoa(unit) + " unidade(s) de " + i.name)
}
}
type customerType struct {
customerId int
name string
}
var wg sync.WaitGroup
func main() {
shirt := itemType{441, "camisa", 1000, 10}
customerA := customerType{1, "A"}
customerB := customerType{2, "B"}
customerC := customerType{3, "C"}
wg.Add(3)
go routineCustomer(&shirt, &customerA, 4)
go routineCustomer(&shirt, &customerB, 5)
go routineCustomer(&shirt, &customerC, 3)
wg.Wait()
}
func routineCustomer(shirt *itemType, customer *customerType, unit int) {
shirt.takeItem(customer, unit)
wg.Done()
}
Dado que a camisa tem apenas 10 unidades e três clientes querem comprar um total de 12 unidades. A primeira resposta é que o Cliente C comprou 3 unidades de camisa e a quantidade restante no estoque é de 2 unidades. Porém, se o total de camisas é 10 unidades e o Cliente C comprou 3 unidades, deveria haver 7 unidades no estoque. Após o Cliente C, temos o Cliente A que não conseguiu comprar 4 camisas e, por fim, o Cliente B que conseguiu comprar 5 unidades, mostrando na tela que ainda há 5 unidades em estoque. Isso demonstra um problema de race condition onde temos threads/processos competindo por um recurso.
Adicionando o controle de concorrência com mutex
Agora queremos que cada cliente que estiver com o item possa subtrair uma unidade desse item sem que nenhuma outra goroutine esteja acessando e modificando a quantidade do item.
package main
import (
"fmt"
"strconv"
"sync"
"time"
)
type itemType struct {
itemId int
name string
price int
unit int
}
func (i *itemType) takeItem(customer *customerType, unit int) {
mu.Lock()
// Simula um atraso para aumentar a chance de condição de corrida
time.Sleep(50 * time.Millisecond)
if i.unit > 0 && i.unit >= unit {
i.unit = i.unit - unit
fmt.Println("O Cliente " + customer.name + " comprou " + strconv.Itoa(unit) + " unidade(s) de " + i.name + " e a quantidade restante no estoque é " + strconv.Itoa(i.unit))
} else {
fmt.Println("O Cliente " + customer.name + " não conseguiu comprar " + strconv.Itoa(unit) + " unidade(s) de " + i.name)
}
mu.Unlock()
}
type customerType struct {
customerId int
name string
}
var wg sync.WaitGroup
var mu sync.Mutex
func main() {
shirt := itemType{441, "camisa", 1000, 10}
customerA := customerType{1, "A"}
customerB := customerType{2, "B"}
customerC := customerType{3, "C"}
wg.Add(3)
go routineCustomer(&shirt, &customerA, 4)
go routineCustomer(&shirt, &customerB, 5)
go routineCustomer(&shirt, &customerC, 3)
wg.Wait()
}
func routineCustomer(shirt *itemType, customer *customerType, unit int) {
shirt.takeItem(customer, unit)
wg.Done()
}
Agora temos como saída o Cliente B comprando 5 unidades e a quantidade do estoque sendo 5, logo após o Cliente C comprando 3 unidades e a quantidade do estoque sendo 2, e por fim, o Cliente A não consegue comprar 4 unidades porque só existem 2 unidades disponíveis.
Nesse caso, o que ocorre é o seguinte: quando uma goroutine tenta subtrair a unidade do item, ela deve chamar o método Lock do mutex para adquirir uma trava exclusiva. Se outra goroutine tiver adquirido a trava, essa operação ficará bloqueada até a outra goroutine chamar Unlock e a trava tornar-se disponível novamente. O mutex protege as variáveis compartilhadas.
A região de código entre Lock e Unlock, em que a goroutine é livre para ler e modificar as variáveis compartilhadas, é chamada de seção crítica.
Link do Código
Github: https://github.com/jcelsocosta/race_condition/tree/main
Referências
- DONOVAN, Alan A. A.; KERNIGHAN, Brian W. A Linguagem de Programação Go. 1. ed. São Paulo: Novatec, 2016. 400 p.
Top comments (0)