Это одновременно, но что заставляет его работать параллельно?

#go

#Вперед

Вопрос:

Итак, я пытаюсь понять, как работают параллельные вычисления, одновременно изучая Go. Я понимаю разницу между параллелизмом и параллелизмом, однако я немного зациклен на том, как Go (или ОС) определяет, что что-то должно выполняться параллельно…

Есть ли что-то, что я должен делать при написании своего кода, или все это обрабатывается планировщиками?

В приведенном ниже примере у меня есть две функции, которые выполняются в отдельных подпрограммах Go с использованием ключевого слова go . Потому что GOMAXPROCS по умолчанию — это количество процессоров, доступных на вашем компьютере (и я также явно его устанавливаю) Я бы ожидал, что эти две функции будут выполняться одновременно, и, следовательно, результат будет представлять собой сочетание чисел в определенном порядке — и, кроме того, при каждом запуске результат будет отличаться. Однако это не так. Вместо этого они выполняются один за другим, и, чтобы еще больше запутать ситуацию, функция two выполняется перед функцией one .

Код:

 func main() {
    runtime.GOMAXPROCS(6)
    var wg sync.WaitGroup
    wg.Add(2)

    fmt.Println("Starting")
    go func() {
        defer wg.Done()
        for smallNum := 0; smallNum < 20; smallNum   {
            fmt.Printf("%v ", smallNum)
        }
    }()

    go func() {
        defer wg.Done()
        for bigNum := 100; bigNum > 80; bigNum-- {
            fmt.Printf("%v ", bigNum)
        }
    }()

    fmt.Println("Waiting to finish")
    wg.Wait()

    fmt.Println("nFinished, Now terminating")
}
  

Вывод:

 go run main.go
Starting
Waiting to finish
100 99 98 97 96 95 94 93 92 91 90 89 88 87 86 85 84 83 82 81 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Finished, Now terminating
  

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

Работает ли это так, как должно, и я что-то неправильно понимаю, или мой код неправильный?

Комментарии:

1. Сколько раз вы на самом деле пробовали это?

2. Ваш код выглядит нормально. Запуск play.golang.org/p/1OslDJyfwN6 несколько раз дает мне разные результаты.

3. Есть очень хорошая статья о планировании go — ardanlabs.com/blog/2018/08/scheduling-in-go-part1.html

Ответ №1:

Есть ли что-то, что я должен делать при написании своего кода,

Нет.

или все это обрабатывается планировщиками?

ДА.

В приведенном ниже примере у меня есть две функции, которые выполняются в отдельных подпрограммах Go с использованием ключевого слова go . Потому что GOMAXPROCS по умолчанию — это количество процессоров, доступных на вашем компьютере (и я также явно его устанавливаю) Я бы ожидал, что эти две функции будут выполняться одновременно

Они могут или не могут, у вас здесь нет контроля.

и, таким образом, результат будет представлять собой сочетание чисел в определенном порядке — и, кроме того, при каждом запуске результат будет отличаться. Однако это не так. Вместо этого они выполняются один за другим, и, чтобы еще больше запутать ситуацию, функция two выполняется перед функцией one .

ДА. Опять же, вы не можете принудительно выполнять параллельные вычисления.

Ваш тест ошибочен: вы просто мало что делаете в каждой подпрограмме. В вашем примере может быть запланирован запуск goroutine 2, который запускается и завершается до запуска goroutine 1. «Запуск» подпрограммы с go помощью не заставляет ее сразу начинать выполнение, все, что делается, — это создание новой подпрограммы, которая может выполняться. Из всех программ, которые могут выполняться, некоторые запланированы на ваших процессорах. Все это планирование невозможно контролировать, оно полностью автоматическое. Как вы, кажется, знаете, в этом разница между параллельным и параллельным. У вас есть контроль над параллелизмом в Go, но не (много) над тем, что фактически выполняется параллельно на двух или более ядрах.

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

Ответ №2:

Все это обрабатывается планировщиком.

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

Вот еще один игрушечный пример: https://play.golang.org/p/xPKITzKACZp

 package main

import (
    "fmt"
    "runtime"
    "sync"
    "sync/atomic"
    "time"
)

const (
    ConstMaxProcs  = 2
    ConstRunners   = 4
    ConstLoopcount = 1_000_000
)

func runner(id int, wg *sync.WaitGroup, cptr *int64) {
    var times int
    for i := 0; i < ConstLoopcount; i   {
        val := atomic.AddInt64(cptr, 1)
        if val > 1 {
            times  
        }
        atomic.AddInt64(cptr, -1)
    }

    fmt.Printf("[runner %d] cptr was > 1 on %d occasionsn", id, times)
    wg.Done()
}

func main() {
    runtime.GOMAXPROCS(ConstMaxProcs)

    var cptr int64

    wg := amp;sync.WaitGroup{}
    wg.Add(ConstRunners)

    start := time.Now()
    for id := 1; id <= ConstRunners; id   {
        go runner(id, wg, amp;cptr)
    }

    wg.Wait()
    fmt.Printf("completed in %sn", time.Now().Sub(start))
}
  

Как и в вашем примере: у вас нет контроля над планировщиком, в этом примере просто больше «поверхности», чтобы засвидетельствовать некоторые эффекты параллелизма.


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

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

Обратите внимание, что некоторые другие эффекты (возможно: предсказание ветвления при if val > 1 {...} проверке и / или аннулирование памяти вокруг общей cptr переменной) делают выполнение на моей машине очень нестабильным, поэтому не ожидайте прямого «выполнение с ConstMaxProcs = 4 в 4 раза быстрее, чем ConstMaxProcs = 1».