Я использую golang append в goroutine в среде продукта. Я знаю, что не должен его использовать, потому что он небезопасен для параллелизма. Но при его использовании сталкиваюсь с паникой. Это довольно странно. У кого-нибудь есть идеи?
package main
import (
"sync"
)
func TestAppend() (result []int) {
var wg sync.WaitGroup
//result = make([]int, 0, 10) //set enough capacity first, will not add capacity, it will not panic
for i := 0; i < 100; i++ {
v := i
wg.Add(1)
go func() {
defer wg.Done()
result = append(result, v)
}()
}
wg.Wait()
return result
}
func main() {
for a := 0; a < 100000; a++ {
res := TestAppend()
println("len(res):", len(res))
}
}
версия Golang - 1.4.
Я знаю, что длина не ожидается, потому что использование добавления в параллелизме небезопасно. Но иногда бывает паника, что меня смущает.
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x10577ef]
goroutine 8996815 [running]:
main.TestAppend.func1(0xc000092000, 0xc00000c020, 0x9)
/Users/left-pocket/go-practice/concurrency_append/main.go:15 +0x6f
created by main.TestAppend
/Users/left-pocket/go-practice/concurrency_append/main.go:13 +0xa6
Process finished with exit code 2
Я отлаживаю код, обнаружил, что код паники
func growslice(et *_type, old slice, cap int) slice {
...
memmove(p, old.array, lenmem)
...
}
Похоже, у memmove
есть шанс запаниковать при использовании добавления в параллелизме.
Но не уверен, в чем причина.
У кого-нибудь есть идеи?
Итак, вы знаете, что это небезопасно для параллелизма, и затем вы спрашиваете, почему возникает паника ... ??
Хм, я знаю, что параллелизм небезопасен и должен привести к неожиданному результату. Но просто хочу понять первопричину, которая вызывает панику.
«Но просто хочу понять основную причину, которая вызывает панику» Основная причина находится в верхней части сообщения об ошибке: «недопустимый адрес памяти или разыменование нулевого указателя» Что неясно по этой причине?
Основная причина в том, что несинхронизированное чтение и запись в общую переменную несколькими читателями и писателями приводит к неопределенному поведению. Выполняемые инструкции больше не синхронизируются с состоянием памяти. В вашем случае это приводит к исключению nil ptr, в других случаях это может незаметно заменить вызов функции во время выполнения (software.intel.com/content/www/us/en/develop/blogs/…). Может быть, вы хотите узнать больше о внутреннем устройстве компьютерных языков, я не уверен, что это подходящее место, но мне действительно нравится этот вопрос.
Обратите внимание, что значение заголовка среза состоит из трех частей: указателя (на основание некоторой области в памяти, которая может содержать значения), длины и емкости. При использовании result = append(result, v)
функция append () возвращает, по крайней мере, логически, новое значение заголовка фрагмента - другими словами, новый набор из трех значений - и эти три значения могут быть «подключены» к переменной в любом порядке. Если порядок следующий: «сначала новая емкость, затем новая длина и / или указатель», второй append
может решить, что существующий указатель (возможно, ноль) имеет место для нескольких элементов, и выбрать запись в ноль.
Фактическую операцию довольно сложно предсказать: нам нужно точно знать, что вышло из компилятора, какие именно инструкции ЦП выполняются, на каких ядрах и в какое время, а в некоторых случаях точно, что находится в каких кешах и в каких ЦП. Поэтому редко стоит пытаться предсказать, что произойдет мощь. Несколько чаще бывает полезно диагностировать неисправность, так сказать, вскрытие, но даже это может быть довольно сложно.
Это паника, потому что это гонка данных ... Вы уже ответили на свой вопрос. software.intel.com/content/www/us/en/develop/blogs/…