Перечисленные здесь общие ямки в языке Go соответствуют синтаксису языка Go и могут быть скомпилированы обычным образом, но текущий результат может быть неправильным или существует риск утечки ресурсов.
Массивы передаются по значению
В параметрах вызова функции массив передается по значению, и результат нельзя вернуть, изменив параметр типа массива.
func main() {
x := [3]int{1, 2, 3}
func(arr [3]int) {
arr[0] = 7
fmt.Println(arr)
}(x)
fmt.Println(x)
}
Срезы нужно использовать по мере необходимости.
обход карты не в фиксированном порядке
MAP — это реализация хеш-таблицы, и порядок каждого обхода может быть разным.
func main() {
m := map[string]string{
"1": "1",
"2": "2",
"3": "3",
}
for k, v := range m {
println(k, v)
}
}
возвращаемое значение маскируется
В локальной области именованное возвращаемое значение маскирует локальную переменную с тем же именем:
func Foo() (err error) {
if err := Bar(); err != nil {
return
}
return
}
восстановление должно быть запущено в функции отсрочки
recovery перехватывает исключение при вызове прародителя и недействительно при прямом вызове:
func main() {
recover()
panic(1)
}
Прямые вызовы отсрочки также недействительны:
func main() {
defer recover()
panic(1)
}
Многоуровневая вложенность по-прежнему недействительна, когда вызывается defer:
func main() {
defer func() {
func() { recover() }()
}()
panic(1)
}
Должен вызываться непосредственно в функции Defer:
func main() {
defer func() {
recover()
}()
panic(1)
}
Основная функция выходит рано
Фоновые горутины не гарантируют выполнение задач.
func main() {
go println("hello")
}
Избегайте проблем параллелизма с помощью Sleep
Hibernate не гарантирует вывод полной строки:
func main() {
go println("hello")
time.Sleep(time.Second)
}
Точно так же, вставив операторы отправки:
func main() {
go println("hello")
runtime.Gosched()
}
Эксклюзивный ЦП заставляет других горутин умирать от голода
Горутина — это совместное планирование, и сама Горутина не будет активно отдавать ЦП:
func main() {
runtime.GOMAXPROCS(1)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}()
for {} // 占用CPU
}
Решение состоит в том, чтобы добавить функцию планирования Runtime.gosched () в цикл для цикла:
func main() {
runtime.GOMAXPROCS(1)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}()
for {
runtime.Gosched()
}
}
Или избегайте использования ЦП, блокируя:
func main() {
runtime.GOMAXPROCS(1)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}()
select{}
}
Модель памяти последовательной согласованности не удовлетворяется между различными горутинами.
Потому что в разных горутинах функция main может не иметь возможности наблюдать за изменением состояния done, поэтому цикл for попадет в бесконечный цикл:
var msg string
var done bool = false
func main() {
runtime.GOMAXPROCS(1)
go func() {
msg = "hello, world"
done = true
}()
for {
if done {
println(msg)
break
}
}
}
Решение состоит в том, чтобы использовать синхронизацию дисплея:
var msg string
var done = make(chan bool)
func main() {
runtime.GOMAXPROCS(1)
go func() {
msg = "hello, world"
done <- true
}()
<-done
println(msg)
}
Ошибка закрытия относится к той же переменной
func main() {
for i := 0; i < 5; i++ {
defer func() {
println(i)
}()
}
}
Улучшенный подход заключается в создании локальной переменной на каждой итерации.
func main() {
for i := 0; i < 5; i++ {
i := i
defer func() {
println(i)
}()
}
}
Или передать его как параметр функции
func main() {
for i := 0; i < 5; i++ {
defer func(i int) {
println(i)
}(i)
}
}
Выполнить оператор отсрочки внутри цикла
defer может выполняться только при выходе из функции, а выполнение defer в for вызовет задержку высвобождения ресурсов:
func main() {
for i := 0; i < 5; i++ {
f, err := os.Open("/path/to/file")
if err != nil {
log.Fatal(err)
}
defer f.Close()
}
}
Решение состоит в том, чтобы создать локальную функцию в for и выполнить defer внутри локальной функции:
func main() {
for i := 0; i < 5; i++ {
func() {
f, err := os.Open("/path/to/file")
if err != nil {
log.Fatal(err)
}
defer f.Close()
}()
}
}
Нарезка приводит к блокировке всего базового массива.
Нарезка приведет к блокировке всего базового массива, и базовый массив не сможет освободить память.Если базовый массив большой, это вызовет большую нагрузку на память.
func main() {
headerMap := make(map[string][]byte)
for i := 0; i < 5; i++ {
name := "/path/to/file"
data, err := ioutil.ReadFile(name)
if err != nil {
log.Fatal(err)
}
headerMap[name] = data[:1]
}
// do some thing
}
Решение состоит в том, чтобы клонировать результат, который освобождает базовый массив:
func main() {
headerMap := make(map[string][]byte)
for i := 0; i < 5; i++ {
name := "/path/to/file"
data, err := ioutil.ReadFile(name)
if err != nil {
log.Fatal(err)
}
headerMap[name] = append([]byte{}, data[:1]...)
}
// do some thing
}
Нулевой указатель и нулевой интерфейс не эквивалентны
Например, возвращается указатель ошибки, но это не пустой интерфейс ошибки:
func returnsError() error {
var p *MyError = nil
if bad() {
p = ErrBad
}
return p // Will always return a non-nil error.
}
изменения адреса памяти
Адреса объектов в Go могут меняться, поэтому указатели нельзя генерировать из других типов, не являющихся указателями:
func main() {
var x int = 42
var p uintptr = uintptr(unsafe.Poiner(&x))
runtime.GC()
var px *int = (*int)(unsafe.Poiner(p))
println(*px)
}
При изменении памяти связанный указатель будет обновляться синхронно, но uintptr не указателя не будет обновляться синхронно.
Точно так же адрес объектов Go нельзя хранить в cgo.
Утечка горутин
В языке Go есть функция автоматического перезапуска памяти, поэтому утечки памяти обычно не происходит. Тем не менее, горутины действительно утекают, и память, на которую ссылаются просочившиеся горутины, также не может быть восстановлена.
func main() {
ch := func() <-chan int {
ch := make(chan int)
go func() {
for i := 0; ; i++ {
ch <- i
}
} ()
return ch
}()
for v := range ch {
fmt.Println(v)
if v == 5 {
break
}
}
}
В приведенной выше программе фоновая горутина вводит последовательность натуральных чисел в конвейер, и эта последовательность выводится в основной функции. Но когда break выходит из цикла for, фоновая горутина находится в состоянии, которое нельзя перезапустить.
Мы можем избежать проблемы с пакетом contxt:
func main() {
ctx, cancel := context.WithCancel(context.Background())
ch := func(ctx context.Context) <-chan int {
ch := make(chan int)
go func() {
for i := 0; ; i++ {
select {
case <- ctx.Done():
return
case ch <- i:
}
}
} ()
return ch
}(ctx)
for v := range ch {
fmt.Println(v)
if v == 5 {
cancel()
break
}
}
}
Когда основная функция выходит из цикла при разрыве, вызываяcancel()
Чтобы уведомить фоновую горутину о выходе, чтобы избежать утечки горутины.