GO: Параллелизм и параллелизм

Параллелизм и параллелизм в GO был фактически первым предметом, который заинтересовал меня в изучении этого языка, хотя моей первоначальной целью было посмотреть, как он справляется с параллелизмом, только сегодня я действительно изучал его.

Различия между параллелизмом и параллельностью

При параллелизме процессы запускаются одновременно, но так как используется только одно ядро, один процесс останавливается, чтобы мог запуститься другой, а затем они чередуются.

При параллелизме два процесса запускаются одновременно, но каждый из них обрабатывается в отдельном ядре процессора, так что два процесса могут обрабатываться одновременно.

Перейти к рутине

  func main() {
    runProcess("Process 1", 20)
    runProcess("Process 2", 20)

}

func runProcess(name string, total int) {
    for i := 0; i < total; i++ {
        fmt.Println(name, i)
        t := time.Duration(rand.Intn(255))
        time.Sleep(time.Millisecond * t)
    }
}

Войдите в полноэкранный режим Выход из полноэкранного режима

Эта функция, определенная выше таким начальным образом, сначала 20 раз напечатает на терминале «Процесс 1», всегда ожидая случайного времени в миллисекундах для второй печати, а затем еще 20 раз напечатает «Процесс 2».

Добавление оператора go перед каждым из вызовов функций приведет к тому, что они будут выполняться параллельно и в фоновом режиме. Теперь вместо того, чтобы ждать выполнения первой функции и затем запускать вторую, с go перед ней, пока одна функция ждет истечения таймаута, другая выполняется (если бы мы не определили таймаут, так как это очень простая функция, к тому времени, когда программа достигнет второй функции, первая уже закончит выполняться, и мы не сможем увидеть действие процедуры go).

но если мы просто добавим go перед функцией, терминал ничего не покажет, потому что функции работают в фоновом режиме, так что на самом деле go будет думать, что они уже закончили работу и завершит выполнение. Чтобы решить эту проблему, нам нужно создать группу waitGroup и заставить go ждать окончания выполнения функции, а затем убить приложение.


var waitGroup sync.WaitGroup

func main() {
    waitGroup.Add(2)

    go runProcess("Process 1", 20)
    go runProcess("Process 2", 20)

    waitGroup.Wait()

}

func runProcess(name string, total int) {
    for i := 0; i < total; i++ {
        fmt.Println(name, i)
        t := time.Duration(rand.Intn(255))
        time.Sleep(time.Millisecond * t)
    }
    waitGroup.Done()
}
Войдите в полноэкранный режим Выход из полноэкранного режима

В main я добавил две функции к waitGroup, а в функции runProcess я говорю, что waitGroup завершила работу после for.

Но теперь вопрос в том, использует ли Go параллелизм или параллелизм? И на самом деле это зависит от того, в данном случае это параллелизм, потому что у меня 6-ядерный процессор, поэтому по умолчанию он связывает каждую функцию с ядром, но если бы у меня было только одно ядро в процессоре, он бы запускал обе функции параллельно.

мы также можем протестировать одновременную работу, добавив функцию init и установив максимальное количество цветов, которое мы разрешаем GO использовать. но если мы сделаем это для данного случая, результат будет таким же

func init() {
    runtime.GOMAXPROCS(1)
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Условия гонки

Условия гонки — это вид проблемы, которую мы имеем с параллелизмом, когда параллельное выполнение кода каким-то образом нарушает бизнес-правила приложения, поэтому, например, если я хочу просмотреть общее количество раз, которое выполнила программа for

var result int
var waitGroup sync.WaitGroup

func main() {

    waitGroup.Add(2)

    go runProcess("Process 1", 20)
    go runProcess("Process 2", 20)

    waitGroup.Wait()
    fmt.Println("Result:", result)
}

func runProcess(name string, total int) {
    for i := 0; i < total; i++ {
        z := result
        z++
        t := time.Duration(rand.Intn(255))
        time.Sleep(time.Millisecond * t)
        result = z
        fmt.Println(name, "->", i, result)
    }
    waitGroup.Done()
}

Войдите в полноэкранный режим Выход из полноэкранного режима

в моем println с отображением результата, который должен быть 40, на самом деле результат будет 20, и мы видим, что значение результата фактически переназначается на предыдущее значение постоянно из-за того, что предыдущее выполнение еще не имеет нового значения, которое должно быть у результата

Если мы запустим программу с помощью go run -race main.go go сам определит, есть ли состояние гонки, и если да, то на каких строках.

для решения проблемы состояния гонки очень просто использовать Mutex, где мы можем заблокировать операцию до ее завершения, чтобы предотвратить ее перезапись в середине процесса

func runProcess(name string, total int) {
    for i := 0; i < total; i++ {

        t := time.Duration(rand.Intn(255))
        time.Sleep(time.Millisecond * t)
        m.Lock()
        result++
        fmt.Println(name, "->", i, "total", result)
                m.Unlock()
    }
    waitGroup.Done()
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Теперь процессы блокируются, ожидая, пока результат закончит увеличивать свое значение, и только когда я даю разблокировку, они продолжаются. Если мы запустим приложение, обнаруживающее условия гонки, то увидим, что теперь ничего не было обнаружено

Оцените статью
Procodings.ru
Добавить комментарий