Это первый раз, когда я участвую в Gengwen Challenge.20День, подробности о событии уточняйте:Обновить вызов
Принцип реализации defer в GO
Давайте рассмотрим последний обмен и поделимся некоторыми знаниями о канале.
- Поделился, что такое канал в GO
- Подробный анализ базовой структуры данных канала
- Как каналы реализованы в исходном коде GO
- Основы чаньского чтения и письма
- Какие исключения будут происходить при закрытии канала,panic
- Простое применение select
если правильноchanЕсли вы все еще заинтересованы в канале, пожалуйста, проверьте статьюРазделение принципов реализации Чана в GO
Что такое отсрочка?
Давайте взглянемdeferчто это такое
это ключевое слово в GO
Это ключевое слово, которое мы обычно используем для высвобождения ресурсов, вreturnпозвоню ему
Если в программе более одногоdefer, последовательность вызова defer выглядит следующим образомкучаПуть,后进先出 LIFOДа, кстати, пишите сюда
- куча
Следуйте принципу «последним пришел – первым вышел»
Войдите в стек позже, сначала извлеките стек
Сначала в стек, потом из стека
- очередь
Следуя принципу «первым пришел — первым вышел», мы можем представить односторонний конвейер, входящий слева и выходящий справа.
первым прибыл, первым обслужен
Приходите позже, уходите позже, не прыгая в очереди
Принцип отсрочки реализации
Давайте сначала сделаем вывод, и давайте немного от души:
- объявлено в коде
deferМестоположение при компиляции вставляет функцию с именемdeferproc, вdeferВставьте возвращаемую функцию перед функцией, в которой она находится, а неreturnо, даdeferreturn
специфическийdeferКаков принцип реализации, мы все те же, посмотримdeferЧто лежит в основе структуры данных
существуетsrc/runtime/runtime2.goизtype _defer struct {структура
// A _defer holds an entry on the list of deferred calls.
// If you add a field here, add code to clear it in freedefer and deferProcStack
// This struct must match the code in cmd/compile/internal/gc/reflect.go:deferstruct
// and cmd/compile/internal/gc/ssa.go:(*state).call.
// Some defers will be allocated on the stack and some on the heap.
// All defers are logically part of the stack, so write barriers to
// initialize them are not required. All defers must be manually scanned,
// and for heap defers, marked.
type _defer struct {
siz int32 // includes both arguments and results
started bool
heap bool
// openDefer indicates that this _defer is for a frame with open-coded
// defers. We have only one defer record for the entire frame (which may
// currently have 0, 1, or more defers active).
openDefer bool
sp uintptr // sp at time of defer
pc uintptr // pc at time of defer
fn *funcval // can be nil for open-coded defers
_panic *_panic // panic that is running defer
link *_defer
// If openDefer is true, the fields below record values about the stack
// frame and associated function that has the open-coded defer(s). sp
// above will be the sp for the frame, and pc will be address of the
// deferreturn call in the function.
fd unsafe.Pointer // funcdata for the function associated with the frame
varp uintptr // value of varp for the stack frame
// framepc is the current pc associated with the stack frame. Together,
// with sp above (which is the sp associated with the stack frame),
// framepc/sp can be used as pc/sp pair to continue a stack trace via
// gentraceback().
framepc uintptr
}
_deferУдерживая запись в списке отложенных вызовов, посмотрим, что означают параметры приведенной выше структуры данных
| tag | иллюстрировать |
|---|---|
| siz | Размер памяти аргументов и результатов функции отсрочки |
| fn | Функции, которые необходимо отложить |
| _panic | структура паники отсрочки |
| link | Функции задержки отсрочки в одной и той же сопрограмме будут связаны друг с другом через этот указатель. |
| heap | Выделяется ли он в куче |
| openDefer | Оптимизирован ли он для открытого кодирования? |
| sp | Указатель стека (обычно соответствует сборке) |
| pc | счетчик команд |
За ключевым словом defer должна следовать функция, мы должны помнить об этом.
Из описания вышеперечисленных параметров мы можем узнать, чтоdeferСтруктура данных аналогична структуре функции и также имеет следующие три параметра:
- указатель стека SP
- Счетчик программ ПК
- адрес функции
Но мы также узнали, что среди участников есть еще одинlink, функция задержки задержки в той же сопрограмме будет связана вместе через этот указатель
этоlinkуказатель, это указатель наdeferГлава односвязного списка, каждый раз, когда мы объявляемdeferкогдаdeferДанные вставляются в позицию заголовка этого односвязного списка,
Затем выполнитеdefer, мы можем предположитьdeferКак ты получил это?
упомянутый ранееdeferПоследний пришел, первый вышел, конечно, этот принцип соблюдается и здесь.deferПри выполнении он извлекается из головы односвязного списка.
Давайте нарисуем картину
объявить в сопрограмме A2индивидуальныйdefer, объявить сначалаdefer test1()
перефразироватьdefer test2()
Видно, что утверждение послеdeferбудет вставлен в начало односвязного списка, первый объявленныйdeferстоял в очереди сзади
Когда мы извлекаем, мы всегда извлекаем голову и выполняем ее до тех пор, пока односвязный список не станет пустым.
Давайте взглянемdeferконкретная реализация
Исходные файлы находятся вsrc/runtime/panic.go, см. функциюdeferproc
// Create a new deferred function fn with siz bytes of arguments.
// The compiler turns a defer statement into a call to this.
//go:nosplit
func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn
gp := getg()
if gp.m.curg != gp {
// go code on the system stack can't defer
throw("defer on system stack")
}
// the arguments of fn are in a perilous state. The stack map
// for deferproc does not describe them. So we can't let garbage
// collection or stack copying trigger until we've copied them out
// to somewhere safe. The memmove below does that.
// Until the copy completes, we can only call nosplit routines.
sp := getcallersp()
argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn)
callerpc := getcallerpc()
d := newdefer(siz)
if d._panic != nil {
throw("deferproc: d.panic != nil after newdefer")
}
d.link = gp._defer
gp._defer = d
d.fn = fn
d.pc = callerpc
d.sp = sp
switch siz {
case 0:
// Do nothing.
case sys.PtrSize:
*(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp))
default:
memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz))
}
// deferproc returns 0 normally.
// a deferred func that stops a panic
// makes the deferproc return 1.
// the code the compiler generates always
// checks the return value and jumps to the
// end of the function if deferproc returns != 0.
return0()
// No code can go here - the C return register has
// been set and must not be clobbered.
}
deferprocРоль:
Создать новую отложенную функциюfn, аргумент равен siz байтам, компилятор преобразует отложенный оператор в паруthisвызов
getcallersp():
получатьdeferprocпредыдущийrspЗначение регистра, способ его реализации одинаков для всех платформ.
//go:noescape
func getcallersp() uintptr // implemented as an intrinsic on all platforms
callerpc := getcallerpc():
иди сюдаrspПосле этого хранить вcallerpc, здесь звонитьdeferprocследующая инструкция
d := newdefer(siz):
d := newdefer(siz)создать новыйdeferструктура, последующий код приведен вdeferназначение члена этой структуры
Мы смотримdeferprocОбщий процесс:
- Получать
deferprocЗначение предыдущего регистра rsp - использовать
newdeferВыделите объект структуры _defer и поместите его в текущий_deferглава связанного списка - Инициализируйте соответствующие параметры члена _defer
- return0
приди и посмотриnewdeferисходный код
Исходные файлы находятся вsrc/runtime/panic.go, см. функциюnewdefer
// Allocate a Defer, usually using per-P pool.
// Each defer must be released with freedefer. The defer is not
// added to any defer chain yet.
//
// This must not grow the stack because there may be a frame without
// stack map information when this is called.
//
//go:nosplit
func newdefer(siz int32) *_defer {
var d *_defer
sc := deferclass(uintptr(siz))
gp := getg()
if sc < uintptr(len(p{}.deferpool)) {
pp := gp.m.p.ptr()
if len(pp.deferpool[sc]) == 0 && sched.deferpool[sc] != nil {
// Take the slow path on the system stack so
// we don't grow newdefer's stack.
systemstack(func() {
lock(&sched.deferlock)
for len(pp.deferpool[sc]) < cap(pp.deferpool[sc])/2 && sched.deferpool[sc] != nil {
d := sched.deferpool[sc]
sched.deferpool[sc] = d.link
d.link = nil
pp.deferpool[sc] = append(pp.deferpool[sc], d)
}
unlock(&sched.deferlock)
})
}
if n := len(pp.deferpool[sc]); n > 0 {
d = pp.deferpool[sc][n-1]
pp.deferpool[sc][n-1] = nil
pp.deferpool[sc] = pp.deferpool[sc][:n-1]
}
}
if d == nil {
// Allocate new defer+args.
systemstack(func() {
total := roundupsize(totaldefersize(uintptr(siz)))
d = (*_defer)(mallocgc(total, deferType, true))
})
}
d.siz = siz
d.heap = true
return d
}
newderferРоль:
Обычно используется пул per-P, выделяющийDefer
каждыйdeferбесплатно выпустить. токdeferни к кому не присоединюсьdeferцепь
getg():
Получить указатель структуры текущей сопрограммы
// getg returns the pointer to the current g.
// The compiler rewrites calls to this function into instructions
// that fetch the g directly (from TLS or from the dedicated register).
func getg() *g
pp := gp.m.p.ptr():
Получить P в текущем рабочем потоке
Затем возьмите несколько объектов из глобального пула объектов и передайте их в пул P.
for len(pp.deferpool[sc]) < cap(pp.deferpool[sc])/2 && sched.deferpool[sc] != nil {
d := sched.deferpool[sc]
sched.deferpool[sc] = d.link
d.link = nil
pp.deferpool[sc] = append(pp.deferpool[sc], d)
}
Нажмите, чтобы увидеть структуру данных пула, на самом деле члены в нем — это то, о чем мы говорили ранее._deferуказатель
вsched.deferpool[sc]это глобальный пул,pp.deferpool[sc]это местный бассейн
mallocgcВыделить место
Если вышеуказанная операция не получает значение d, используйте ее напрямуюmallocgcПереназначить и настроить соответствующие элементыsizа такжеheap
if d == nil {
// Allocate new defer+args.
systemstack(func() {
total := roundupsize(totaldefersize(uintptr(siz)))
d = (*_defer)(mallocgc(total, deferType, true))
})
}
d.siz = siz
d.heap = true
mallocgcСпециально реализовано вsrc/runtime/malloc.goЕсли вам интересно, можете внимательно посмотреть на этот кусок, сегодня мы не будем заострять внимание на этой функции.
// Allocate an object of size bytes.
// Small objects are allocated from the per-P cache's free lists.
// Large objects (> 32 kB) are allocated straight from the heap.
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {}
Посмотрим наконецreturn0
Посмотрим наконецdeferprocРезультат в функции возвращаетсяreturn0()
// return0 is a stub used to return 0 from deferproc.
// It is called at the very end of deferproc to signal
// the calling Go function that it should not jump
// to deferreturn.
// in asm_*.s
func return0()
return0используется изdeferprocвернуть0заглушка
это вdeferprocПоследний вызов функции используется для уведомления вызывающегоGoфункция, на которую он не должен переходитьdeferreturn.
при нормальных обстоятельствахreturn0обычно возвращает 0
Но в исключительных случаяхreturn0функция вернется1, то GO перейдет к выполнениюdeferreturn
просто скажиdeferreturn
deferreturnРоль — это ситуация.deferСвязанный список внутри, вернуть соответствующий буфер или оставить соответствующий пробелGCмелодия переработки
Правила отсрочки в GO
Приведенный выше анализ GOdeferПосле принципа реализации давайте теперь посмотрим на приложение в GO.deferследует соблюдать3Как правило, перечислим:
-
deferСледующая функция называется функцией задержки, и параметры этой функции находятся вdeferКогда заявление объявлено, оно было определено - Выполнение функции задержки основано на принципе «последний пришел – первый вышел», я уже много раз упоминал об этом в статье, это впечатление должно быть очень глубоким, оно появилось первым.
deferПосле казни, после появленияdeferвыполнить первым - Отложенные функции могут повлиять на возвращаемое значение всей функции.
давайте еще объясним, второй пункт выше должен быть прост для понимания, на рисунке выше также показан порядок выполнения
Во-первых, давайте напишем небольшую демонстрацию
Параметры функции задержки находятся вdeferКогда заявление объявлено, оно было определено
func main() {
num := 1
defer fmt.Println(num)
num++
return
}
Не угадаешь, результат1, вы можете скопировать код и запустить его самостоятельно
Третий пункт тоже демо
Отложенные функции могут повлиять на возвращаемое значение всей функции.
func test3() (res int) {
defer func() {
res++
}()
return 1
}
func main() {
fmt.Println(test3())
return
}
В приведенном выше коде мыtest3Возвращаемое значение в функции, мы назвали его заранее, оно должно было возвращать результат как1
но вreturnЗдесь порядок выполнения такой
res = 1
res++
Итак, результат2
Суммировать
- поделился, что такое отсрочка
- Простая иллюстрация стеков и очередей
- Структура данных и принцип реализации defer, отображение конкретного исходного кода
- отложить в GO3правила
Добро пожаловать лайк, подписка, избранное
Друзья, ваша поддержка и поощрение мотивируют меня продолжать делиться и улучшать качество.
Хорошо, вот и на этот раз,В следующий раз поиграем в капчу с GO
Технологии открыты, и наш менталитет должен быть открытым. Примите перемены, живите на солнце и двигайтесь вперед.
ямаленький дьяволенок Нежа, добро пожаловать, лайкайте, подписывайтесь и добавляйте в избранное, увидимся в следующий раз~