ошибка индекса массива при зацикливании - PullRequest
0 голосов
/ 10 мая 2018

Я запускаю процедуры go в цикле для двух функций, используя sync, чтобы дождаться завершения процедур go, а затем запускаю обычную функцию вне цикла, например:

func fetchStudentsAndTeachers(db *sqlx.DB, token string) {
    var students Students
    var teachers Teachers
    wg := &sync.WaitGroup{}
    // defer wg.Wait()
    tch := make(chan Teachers)
    schoolList := fetchActiveOrganization(DB)
    std := make(chan Students)
    for key, value := range schoolList {
        value2 := value
        fmt.Println(key, ":", value)
        wg.Add(1)
        go func() {
            defer wg.Done()
            std <- fetchStudentsFromSchool(wg, value2.CleverSchoolID, token)
        }()
        wg.Add(1)
        go func() {
            defer wg.Done()
            tch <- fetchTeachersFromSchool(wg, value2.CleverSchoolID, token)
        }()
        students = <-std
        // teachers = <-tch
    }
    wg.Wait() // It panics after this point
    UpdateOrganizationsAndUsers(DB)
    close(std)
    close(tch)
    fmt.Println(students)
    fmt.Println(teachers)
}

Теперь проблема в том, что когда я выхожу из цикла, это дает мне индекс из-за связанной ошибки. Я проверил, использует ли отладчик delve на wg.Wait() После передачи управления отладчиком с wg.Wait(). Это паника, говоря:

паника: ошибка времени выполнения: индекс выходит за пределы диапазона

(Edited) Примечание. Эта проблема связана с тем, что цикл повторяется один раз и выполняется процедура, которая обрабатывает базы данных. Но каким-то образом цикл повторяется еще раз до завершения процедуры, что вызывает ошибку. Что я должен сделать, чтобы завершить обе процедуры перед следующей итерацией.

1 Ответ

0 голосов
/ 10 мая 2018

Если вы хотите, чтобы 2 рабочие программы завершились до начала следующей итерации, просто переместите вызов wg.Wait() в конец тела цикла:

for key, value := range schoolList {
    value2 := value
    fmt.Println(key, ":", value)
    wg.Add(1)
    go func() {
        defer wg.Done()
        std <- fetchStudentsFromSchool(wg, value2.CleverSchoolID, token)
    }()
    wg.Add(1)
    go func() {
        defer wg.Done()
        tch <- fetchTeachersFromSchool(wg, value2.CleverSchoolID, token)
    }()
    students = <-std
    teachers = <-tch
    wg.Wait()
}

Также обратите внимание, что если вы уже используете каналы для доставки результатов процедуры, и если никто больше не использует каналы std и tch, WaitGroup даже не требуется:

for key, value := range schoolList {
    value2 := value
    fmt.Println(key, ":", value)
    go func() {
        std <- fetchStudentsFromSchool(wg, value2.CleverSchoolID, token)
    }()
    go func() {
        tch <- fetchTeachersFromSchool(wg, value2.CleverSchoolID, token)
    }()
    students = <-std
    teachers = <-tch
}

Этого достаточно, потому что следующая итерация может начаться, только если завершены оба приема от std и tch, но это может произойти, только если рабочие программы выполняют свою работу и отправляют результат по этим каналам.

Теперь, если мы подумаем над тем, что это делает: процедура цикла ожидает двух рабочих процедур, чтобы завершить свою работу и затем перейти (к следующей итерации). Пока 2 рабочих работают, он просто ждет.

Мы можем упростить и улучшить это, выполнив работу 1 рабочего в цикле цикла, а когда закончите, подождите, пока один рабочий также завершит работу (если еще не закончил).

Вот как это может выглядеть:

for key, value := range schoolList {
    value2 := value
    fmt.Println(key, ":", value)
    go func() {
        std <- fetchStudentsFromSchool(wg, value2.CleverSchoolID, token)
    }()
    teachers = fetchTeachersFromSchool(wg, value2.CleverSchoolID, token)
    students = <-std
}

Мы просто выбираем учителей в цикле цикла, и только выбираем студентов в параллельной программе. Это имеет тот же эффект (студенты и преподаватели выбираются одновременно), с меньшими накладными расходами и более чистым кодом.

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

for key, value := range schoolList {
    fmt.Println(key, ":", value)
    go func() {
        std <- fetchStudentsFromSchool(wg, value.CleverSchoolID, token)
    }()
    teachers = fetchTeachersFromSchool(wg, value.CleverSchoolID, token)
    students = <-std
}

(Это относится и к решению с waitgroup.)

...