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.

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs