DEV Community

Celso Costa
Celso Costa

Posted on

Race Condition (Condição de Corrida)

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

Enter fullscreen mode Exit fullscreen mode

Image description

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

Enter fullscreen mode Exit fullscreen mode

Image description

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

  1. 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)