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

Я не совсем уверен, что такое "func literal", поэтому эта ошибка меня немного сбивает с толку. Я думаю, что вижу проблему - я ссылаюсь на переменную значения диапазона изнутри новой процедуры go, поэтому значение может измениться в любое время и не быть тем, что мы ожидаем. Какой лучший способ решить проблему?

Код, о котором идет речь:

func (l *Loader) StartAsynchronous() []LoaderProcess {
    for _, currentProcess := range l.processes {
        cmd := exec.Command(currentProcess.Command, currentProcess.Arguments...)
        log.LogMessage("Asynchronously executing LoaderProcess: %+v", currentProcess)
        go func() {
            output, err := cmd.CombinedOutput()
            if err != nil {
                log.LogMessage("LoaderProcess exited with error status: %+v\n %v", currentProcess, err.Error())
            } else {
                log.LogMessage("LoaderProcess exited successfully: %+v", currentProcess)
                currentProcess.Log.LogMessage(string(output))
            }
            time.Sleep(time.Second * TIME_BETWEEN_SUCCESSIVE_ITERATIONS)
        }()
    }
    return l.processes
}

Мое предложение:

func (l *Loader) StartAsynchronous() []LoaderProcess {
    for _, currentProcess := range l.processes {
        cmd := exec.Command(currentProcess.Command, currentProcess.Arguments...)
        log.LogMessage("Asynchronously executing LoaderProcess: %+v", currentProcess)
        localProcess := currentProcess
        go func() {
            output, err := cmd.CombinedOutput()
            if err != nil {
                log.LogMessage("LoaderProcess exited with error status: %+v\n %v", localProcess, err.Error())
            } else {
                log.LogMessage("LoaderProcess exited successfully: %+v", localProcess)
                localProcess.Log.LogMessage(string(output))
            }
            time.Sleep(time.Second * TIME_BETWEEN_SUCCESSIVE_ITERATIONS)
        }()
    }
    return l.processes
} 

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

Ответ 1

Не плохо, что это распространенная ошибка для новых пользователей в Go, и да переменный var currentProcess изменяется для каждого цикла, поэтому ваши goroutines будут использовать последний процесс в срезе l.processes, все, что вам нужно сделать, - передать переменную в качестве параметра анонимной функции, например:

func (l *Loader) StartAsynchronous() []LoaderProcess {

    for ix := range l.processes {

        go func(currentProcess *LoaderProcess) {

            cmd := exec.Command(currentProcess.Command, currentProcess.Arguments...)
            log.LogMessage("Asynchronously executing LoaderProcess: %+v", currentProcess)

            output, err := cmd.CombinedOutput()
            if err != nil {
                log.LogMessage("LoaderProcess exited with error status: %+v\n %v", currentProcess, err.Error())
            } else {
                log.LogMessage("LoaderProcess exited successfully: %+v", currentProcess)
                currentProcess.Log.LogMessage(string(output))
            }

            time.Sleep(time.Second * TIME_BETWEEN_SUCCESSIVE_ITERATIONS)

        }(&l.processes[ix]) // passing the current process using index

    }

    return l.processes
}

Ответ 2

Да, что вы сделали, это самый простой способ правильно зафиксировать это предупреждение.

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

Ответ 3

Для тех, кто ищет более простой пример:

Это неправильно:

func main() {
  for i:=0; i<10; i++{
    go func(){
        processValue(i)
    }()
  }
}

func processValue(i int){
  fmt.Println(i)
}

Не совсем ошибка, но может привести к неожиданному поведению, поскольку переменная i, управляющая вашим циклом, может быть изменена из другой подпрограммы go. Это на самом деле go vet команда, которая предупреждает об этом. Go vet помогает точно находить подозрительные конструкции такого рода, использует эвристику, которая не гарантирует, что все отчеты являются подлинными проблемами, но может находить ошибки, не обнаруженные компиляторами. Так что хорошей практикой является запускать его время от времени.

Go Playground запускается , прежде чем запускать код, ознакомьтесь с, вы можете увидеть это в действии здесь.

Это правильно:

func main() {
  for i:=0; i<10; i++{
    go func(differentI int){
        processValue(differentI)
    }(i)
  }
}

func processValue(i int){
  fmt.Println(i)
}

Я специально назвал параметр функции func литерал DifferentI, чтобы было очевидно, что это другая переменная. Это безопасно для одновременного использования, go vet не будет жаловаться, и вы не будете вести себя странно. Вы можете увидеть это в действии здесь. (Вы ничего не увидите, так как печать выполняется на разных подпрограммах, но программа успешно завершится)

И, кстати, func литерал - это, в основном, анонимная функция :)