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.

Speedy emails, satisfied customers

Postmark Image

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Immerse yourself in a wealth of knowledge with this piece, supported by the inclusive DEV Community—every developer, no matter where they are in their journey, is invited to contribute to our collective wisdom.

A simple “thank you” goes a long way—express your gratitude below in the comments!

Gathering insights enriches our journey on DEV and fortifies our community ties. Did you find this article valuable? Taking a moment to thank the author can have a significant impact.

Okay