предисловие
При реализации бизнеса в последнее время необходимо считывать данные и затем обрабатывать их асинхронно, на Go естественно реализовать относительно просто, псевдокод такой:
list := []*Demo{{"a"}, {"b"}}
for _, v := range list {
go func() {
fmt.Println("name="+v.Name)
}()
}
type Demo struct {
Name string
}
Несколько строк кода, которые кажутся очень простыми, не соответствуют нашим ожиданиям.После печати выводится:
name=b
name=b
Не то, что мы ожидали:
name=a
name=b
яма один
Поскольку требования к написанию го все еще невелики, а даосизм еще более неглубок,bug
Я буквально часами искал; сначала я думал, что это проблема с источником данных, и прошел через несколько раундов неуверенности в себе. Короче говоря, процесс не указан первым, давайте посмотрим, как решить эту проблему.
Первый способ — использовать временную переменную:
list := []*Demo{{"a"}, {"b"}}
for _, v := range list {
temp:=v
go func() {
fmt.Println("name="+temp.Name)
}()
}
Таким образом, вывод может быть правильно выведен Фактически, ключ к проблеме можно увидеть из этого метода записи.
Когда первый тип не использует временные переменные, основная сопрограмма запускается быстро, а печатаемая под-сопрограмма может еще не запускаться; когда она начинает выполняться, v здесь присваивается последнее значение.
Поэтому то, что здесь печатается, всегда является последней переменной.
Использование временной переменной скопирует текущее пройденное значение, и, естественно, это не повлияет друг на друга.
Конечно, помимо временных переменных можно использовать и замыкания.
list := []*Demo{{"a"}, {"b"}}
for _, v := range list {
go func(temp *Demo) {
fmt.Println("name="+temp.Name)
}(v)
}
При передаче параметров через замыкание каждыйgoroutine
Все будут хранить копию параметров в собственном стеке, чтобы их можно было различить.
яма два
Есть вторая яма, похожая на эту:
list2 := []Demo{{"a"}, {"b"}}
var alist []*Demo
for _, test := range list2 {
alist = append(alist, &test)
}
fmt.Println(alist[0].Name, alist[1].Name)
Этот код не тот, что мы ожидали:
b b
Но мы можем немного изменить его:
list2 := []Demo{{"a"}, {"b"}}
var alist []Demo
for _, test := range list2 {
fmt.Printf("addr=%p\n", &test)
alist = append(alist, test)
}
fmt.Println(alist[0].Name, alist[1].Name)
addr=0xc000010240
addr=0xc000010240
a b
Кстати, адрес памяти печатается, собственно, по результату, наверное, можно догадаться о причине, адрес памяти, печатаемый каждый раз, один и тот же, поэтому, если мы сохраняем указатель, мы сохраняем содержимое одного и того же адреса памяти в суть, так что то же значение.
А если мы храним только значения, то без указателей такой проблемы нет.
Но что, если вы хотите использовать указатели?
list2 := []Demo{{"a"}, {"b"}}
var alist []*Demo
for _, test := range list2 {
temp := test
//fmt.Printf("addr=%p\n", &test)
alist = append(alist, &temp)
}
fmt.Println(alist[0].Name, alist[1].Name)
Это также просто, то же самое использование временных переменных может быть.
Это можно узнать из официального исходного кода,for range
Это просто синтаксический сахар, и по сути это цикл for, потому что каждый раз, когда один и тот же объект проходит и присваивается, будет такой «улун».
яма отсрочки
for
петля +defer
Это тоже комбинированная яма (хотя использовать ее не рекомендуется), сначала рассмотрим пример:
// demo1
func main() {
a := []int{1, 2, 3}
for _, v := range a {
defer fmt.Println(v)
}
}
// demo2
func main() {
a := []int{1, 2, 3}
for _, v := range a {
defer func() {
fmt.Println(v)
}()
}
}
Вывод отдельно:
//demo1
3
2
1
//demo2
3
3
3
demo1
Результаты хорошо понятны,defer
Можно понять, что оператор выполнения помещается в стек, поэтому представленный результат является первым входящим, последним обслуженным.
иdemo2
, так как это замыкание, замыкание содержит ссылку на переменную v, поэтому v было присвоено последнее значение, когда выполняется окончательное отложенное выполнение, поэтому распечатки одинаковы.
Решение аналогично приведенному выше, и его можно решить, передав параметры:
for _, v := range a {
defer func(v int) {
fmt.Println(v)
}(v)
}
С такими деталями вряд ли можно столкнуться в повседневной разработке, а наиболее вероятная встреча — это собеседование, поэтому знать больше не вредно.
Суммировать
Аналогично первому случаю в цикле forgoroutine
позвони, я думаюIDE
Вполне возможно напомнить, напримерIDEA
Большинство ошибок, которые могут быть сделаны, включены в текст, с нетерпением жду продолженияgoland
обновление.
Но на самом деле официальный блог о такого рода ошибках им уже напомнил.
GitHub.com/gowaves/go/me…Просто большинство людей, вероятно, не видели его, поэтому мне придется потратить время, чтобы прочитать его после этого.
ps: В последнее время Nuggets голосуют за популярных авторов и ставят ссылку на буддийскую систему для сбора голосов.