В предыдущей главе, Понимая Панику и восстановление, мы обнаружили, что откладываем С ее большой актуальностью или считаем очень необходимым углубиться. Я надеюсь, что в этой главе вы сможете отложить Ключевое слово имеет глубокое понимание, поэтому давайте начнем. Подожди минутку. Пожалуйста, постройтесь. Мы воспользуемся режимом выхода LIFO здесь.
Первоначальный адрес: Глубокое понимание Go отложите
Характеристика
Давайте вкратце рассмотрим это. отложить Основное использование ключевых слов, чтобы у вас было базовое понимание
Отложенный вызов
func main() {
defer log.Println("EDDYCJY.")
log.Println("end.")
}Выходные результаты:
$ go run main.go 2019/05/19 21:15:02 end. 2019/05/19 21:15:02 EDDYCJY.
II. ЛИФО
func main() {
for i := 0; i < 6; i++ {
defer log.Println("EDDYCJY" + strconv.Itoa(i) + ".")
}
log.Println("end.")
}Выходные результаты:
$ go run main.go 2019/05/19 21:19:17 end. 2019/05/19 21:19:17 EDDYCJY5. 2019/05/19 21:19:17 EDDYCJY4. 2019/05/19 21:19:17 EDDYCJY3. 2019/05/19 21:19:17 EDDYCJY2. 2019/05/19 21:19:17 EDDYCJY1. 2019/05/19 21:19:17 EDDYCJY0.
3. Точка времени выполнения
func main() {
func() {
defer log.Println("defer.EDDYCJY.")
}()
log.Println("main.EDDYCJY.")
}Выходные результаты:
$ go run main.go 2019/05/22 23:30:27 defer.EDDYCJY. 2019/05/22 23:30:27 main.EDDYCJY.
IV. Неправильное Обращение
func main() {
defer func() {
if e := recover(); e != nil {
log.Println("EDDYCJY.")
}
}()
panic("end.")
}Выходные результаты:
$ go run main.go 2019/05/20 22:22:57 EDDYCJY.
Анализ исходного кода
$ go tool compile -S main.go
"".main STEXT size=163 args=0x0 locals=0x40
...
0x0059 00089 (main.go:6) MOVQ AX, 16(SP)
0x005e 00094 (main.go:6) MOVQ $1, 24(SP)
0x0067 00103 (main.go:6) MOVQ $1, 32(SP)
0x0070 00112 (main.go:6) CALL runtime.deferproc(SB)
0x0075 00117 (main.go:6) TESTL AX, AX
0x0077 00119 (main.go:6) JNE 137
0x0079 00121 (main.go:7) XCHGL AX, AX
0x007a 00122 (main.go:7) CALL runtime.deferreturn(SB)
0x007f 00127 (main.go:7) MOVQ 56(SP), BP
0x0084 00132 (main.go:7) ADDQ $64, SP
0x0088 00136 (main.go:7) RET
0x0089 00137 (main.go:6) XCHGL AX, AX
0x008a 00138 (main.go:6) CALL runtime.deferreturn(SB)
0x008f 00143 (main.go:6) MOVQ 56(SP), BP
0x0094 00148 (main.go:6) ADDQ $64, SP
0x0098 00152 (main.go:6) RET
...Во-первых, нам нужно выяснить, какому коду он на самом деле соответствует. С помощью ассемблерного кода мы можем узнать, что задействованы следующие методы:
- Во-первых, нам нужно выяснить, какому коду он на самом деле соответствует. С помощью ассемблерного кода мы можем узнать, что задействованы следующие методы:
- Во-первых, нам нужно выяснить, какому коду он на самом деле соответствует. С помощью ассемблерного кода мы можем узнать, что задействованы следующие методы:
Очевидно, что метод выполнения-это правильный человек. Давайте продолжим и посмотрим, что мы сделали по отдельности.
структура данных
Нам нужно представить его, прежде чем мы начнем. отложить Фундаментная единица _defer Структура выглядит следующим образом:
type _defer struct {
siz int32
started bool
sp uintptr // sp at time of defer
pc uintptr
fn *funcval
_panic *_panic // panic that is running defer
link *_defer
}
...
type funcval struct {
fn uintptr
// variable-size, fn-specific data here
}- Размер: Общий размер всех входящих параметров
- Начало:
отложитьРеализовано ли это? - Sp: Регистр указателя стека функций, обычно указывающий на верхнюю часть текущего стека функций
- ПК: Счетчик программ, иногда называемый указателем команд (IP), используется потоками для отслеживания следующей выполняемой инструкции. В большинстве процессоров ПК указывает на следующую инструкцию, а не на текущую.
- Fn: Адреса функций и параметры, указывающие на входящие
- _ паника: указывая
_паниксвязанный список - Ссылка: указание
_deferсвязанный список
отсрочка
func deferproc(siz int32, fn *funcval) {
...
sp := getcallersp()
argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn)
callerpc := getcallerpc()
d := newdefer(siz)
...
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))
}
return0()
}- Получить вызов
отложитьУказатель стека функций функции, конкретный адрес параметров входящей функции и ПК (счетчик программ), который является следующей выполняемой инструкцией. Они эквивалентны предварительным параметрам для последующего управления потоком. - Создайте новый
отложитеМинимальный элемент_deferЗаполните ранее подготовленные параметры - вызов
memmoveСохраните входящие параметры в новый_defer(текущее использование) для облегчения последующего использования - Последний звонок
возврат 0Эта функция очень важна для возврата. Может избежать/| отсрочкиИз-за возвратавозвратаИ вызватьотсрочкуВызов метода. Первопричина-это прекращение.паникаМетод задержки вызоветdeferprocВозврат 1, но в механизме, еслиdeferprocЕсли возвращаемое значение не равно 0, оно всегда будет проверять возвращаемое значение и переходить к концу функции. иreturn0Он возвращает 0, поэтому предотвращает дублирование вызовов
Резюме
останься Эта функция будет новой _defer Установите некоторые основные свойства и передайте набор параметров вызывающей функции. Наконец, вызов функции завершается специальным методом возврата . Кроме того, этот раздел имеет некоторое отношение к логике обработки предыдущего “Глубокое понимание паники и восстановления”. На самом деле это gp.sched.ret Возврат 0 или 1 шунтов к различным методам обработки
новое определение
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 {
...
lock(&sched.deferlock)
d := sched.deferpool[sc]
unlock(&sched.deferlock)
}
...
}
if d == nil {
systemstack(func() {
total := roundupsize(totaldefersize(uintptr(siz)))
d = (*_defer)(mallocgc(total, deferType, true))
})
...
}
d.siz = siz
d.link = gp._defer
gp._defer = d
return d
}- Возможность использования из пула
_deferМультиплексирование в качестве нового базового блока - Если он недоступен в пуле, позвоните
mallocgcПовторно подайте заявку на новую - Настройте
отложитеОсновные атрибуты и, наконец, измените текущуюподпрограммуИз_deferточки
Таким образом, мы можем отметить два момента, а именно::
отложитеиGoroutine(g)Это напрямую связано, поэтому это обсуждается.отложитьВремя в принципе неотделимоgАктуальность- Новое
отложитьВсегда в верхней части существующего списка, то естьотложитьХарактеристика ЛИФО
Резюме
Эта функция в основном берет на себя приобретение новых. _дефер Роль этого может быть из deferpool Он также может быть применен повторно.
отложить возвращение
func deferreturn(arg0 uintptr) {
gp := getg()
d := gp._defer
if d == nil {
return
}
sp := getcallersp()
if d.sp != sp {
return
}
switch d.siz {
case 0:
// Do nothing.
case sys.PtrSize:
*(*uintptr)(unsafe.Pointer(&arg0)) = *(*uintptr)(deferArgs(d))
default:
memmove(unsafe.Pointer(&arg0), deferArgs(d), uintptr(d.siz))
}
fn := d.fn
d.fn = nil
gp._defer = d.link
freedefer(d)
jmpdefer(fn, uintptr(unsafe.Pointer(&arg0)))
}Если он был вызван в ключевом слове метода defer , то компилятор вставит в конце вызов метода deferr/|. Основными задачами этого метода являются следующие:
- Очистите текущий узел
_deferИнформация о вызове вызываемой функции - Освободите информацию
_deferтекущего узла и поместите ее обратно в пул (легко использовать повторно). - Перейти к вызову
отложитьФункция вызова для ключевых слов
В этом коде метод перехода jmpdefer Особенно важен. Поскольку он явно управляет потоком, код выглядит следующим образом:
// asm_amd64.s
TEXT runtime·jmpdefer(SB), NOSPLIT, $0-16
MOVQ fv+0(FP), DX // fn
MOVQ argp+8(FP), BX // caller sp
LEAQ -8(BX), SP // caller sp after CALL
MOVQ -8(SP), BP // restore BP as if deferreturn returned (harmless if framepointers not in use)
SUBQ $5, (SP) // return to CALL again
MOVQ 0(DX), BX
JMP BX // but first run the deferred functionАнализируя исходный код, мы обнаруживаем, что он сделал две очень “странные” и важные вещи, а именно::
- MOVQ -8(SP), BP:
-8(BX)Это местоположение сохраняется какотложенный возвратАдрес после выполнения - SUBQ $5, (SP):
SPУменьшите адрес на 5, и длина будет точно такой же.время выполнения.отсрочкаДлина
Вы можете спросить, почему 5? ХОРОШО, через полдня я, наконец, посмотрел на ассемблерный код… Ну, вычитание на самом деле не проблема с 5, как показано ниже:
0x007a 00122 (main.go:7) CALL runtime.deferreturn(SB)
0x007f 00127 (main.go:7) MOVQ 56(SP), BPДавайте разберемся в наших мыслях и последуем приведенной выше логике. отложить возврат Это рекурсия. Каждый раз, когда он возвращается Функция отсрочки , когда он закончится, как показано ниже:
func deferreturn(arg0 uintptr) {
gp := getg()
d := gp._defer
if d == nil {
return
}
...
}То есть, они будут продолжать приходить. отложите Функцию, чтобы определить, существует ли еще список _defer . Если он больше не существует, вернитесь и завершите его. Проще говоря, это касается всего этого. отложите Только тогда вы действительно сможете оставить это. Это правда? Давайте еще раз рассмотрим код сборки выше, как показано ниже:
。..
0x0070 00112 (main.go:6) CALL runtime.deferproc(SB)
0x0075 00117 (main.go:6) TESTL AX, AX
0x0077 00119 (main.go:6) JNE 137
0x0079 00121 (main.go:7) XCHGL AX, AX
0x007a 00122 (main.go:7) CALL runtime.deferreturn(SB)
0x007f 00127 (main.go:7) MOVQ 56(SP), BP
0x0084 00132 (main.go:7) ADDQ $64, SP
0x0088 00136 (main.go:7) RET
0x0089 00137 (main.go:6) XCHGL AX, AX
0x008a 00138 (main.go:6) CALL runtime.deferreturn(SB)
...Действительно, поскольку анализ вышеуказанного процесса является последовательным, проверка завершена.
Резюме
Эта функция в основном заботится об очистке использованных. отложить И перейти к вызову отложить Функция ключевого слова очень важна.
резюме
Мы уже упоминали об этом. Ключевое слово defer включает две основные функции, а именно deferproc и deferr/| Функция. и отложенный возврат Функции являются особыми, когда они вызываются прикладной функцией. отложить При использовании ключевых слов компилятор вставляет их в конце отложить Звонки обычно делаются парами.
Но когда один Goroutine Есть много раз отложить Поведение (то есть несколько) _defer ) Компилятор будет использовать некоторые приемы, чтобы вернуться к отложенному возврату Функциям для использования _defer Спискам ссылок не разрешается заканчиваться, пока их не останется
Дополнительные базовые единицы _defer Он может быть использован повторно, или это может быть совершенно новое приложение. В конечном итоге он будет добавлен. _дефер Заголовок списка связанного списка задает функцию последнего вызова и первого вызова
Отношение
- Глубокое понимание паники и восстановления
Справочные ресурсы
- Планирование В Go
- Погрузитесь в стек и отложите/паникуйте/восстанавливайтесь в go
- голанг -примечания