Go определение типа уязвимости и предложения по его предотвращению

Имеется следующий код:

func race(){
    wait := make(chan struct())
    n := 0
    go func () {
        n++
    }()

    n++
    fmt.Println(n)
}

И следующее ТЗ:

К какой уязвимости приведет реализация этого функционала? Опишите уязвимость и предложите свои варианты предотвращения. 

Всё что я могу сказать по этому поводу, так это то что "n" по итогу будет 1, а не 2, а так же может возникнуть ошибка из-за того, что "wait" не используется...

Я вижу данный код следующим образом для корректной отработки:

package main

import "fmt"

func main() {
    wait := make(chan int)
    n := 0
    go func() {
        wait <- 1
    }()
    n += <-wait
    n++
    fmt.Println(n)
}

Но я не думаю, что такое банальное решение является решением. У кого какие идеи и предположения?


Ответы (1 шт):

Автор решения: Nps-rf

Достаточно обратить внимание на название функции :)

Этот код создает ситуацию гонки (race condition).

В данном случае, одновременно выполняются две операции: горутина (go func()) увеличивает n на 1, и основной поток также увеличивает n на 1.

Так как порядок выполнения этих операций не определен, n может быть либо 1, либо 2 в момент вывода, в зависимости от того, какой поток выполнился первым. Это делает поведение программы непредсказуемым и потенциально ошибочным.

Чтобы предотвратить эту уязвимость, можно использовать [механизмы синхронизации, такие как мьютексы (mutexes) или каналы (channels), чтобы гарантировать, что только один поток может изменять переменную в данный момент времени.

Пример с использованием мьютекса:

func race() {
    var mu sync.Mutex
    n := 0
    go func() {
        mu.Lock()
        n++
        mu.Unlock()
    }()

    mu.Lock()
    n++
    mu.Unlock()
    fmt.Println(n)
}

В этом примере mu.Lock() блокирует доступ к переменной n для других потоков, пока текущий поток не вызовет mu.Unlock(), после чего доступ к n становится возможным для других потоков.

Пример с использованием канала:

func race() {
    n := 0
    done := make(chan bool)
    go func() {
        n++
        done <- true
    }()

    n++
    <-done
    fmt.Println(n)
}

Здесь канал done используется для синхронизации потоков. Горутина отправляет значение в канал done после увеличения n, и основной поток ждет это значение перед выводом n, что обеспечивает последовательное увеличение n.

→ Ссылка