Как идти php э, я считаю, что вы правы насчет паники и восстановления Конечно, не странно, но вы когда-нибудь думали об этом? Когда мы выполняем эти два утверждения. Что случилось внизу? Несколько дней назад я просто поболтал со своими коллегами на соответствующие темы и обнаружил, что на самом деле понимание этого у всех все еще расплывчатое. Я надеюсь, что эта статья сможет рассказать вам, почему и что она сделала более подробно.
Первоначальный адрес: Глубокое понимание Паники и восстановления
Отражение
1. Почему операция приостановлена?
func main() {
panic("EDDYCJY.")
}Выходные результаты:
$ go run main.go
panic: EDDYCJY.
goroutine 1 [running]:
main.main()
/Users/eddycjy/go/src/github.com/EDDYCJY/awesomeProject/main.go:4 +0x39
exit status 2Подумайте о том, почему. паника Приведет ли это к остановке работы приложения? (не просто казнь) паника Так что на этом двусмысленности конец.
2. Почему операция не будет приостановлена?
func main() {
defer func() {
if err := recover(); err != nil {
log.Printf("recover: %v", err)
}
}()
panic("EDDYCJY.")
}Выходные результаты:
$ go run main.go 2019/05/11 23:39:47 recover: EDDYCJY.
Подумайте о том, почему. отложить + восстановить Могут ли комбинации защитить приложения?
3. Не откладывайте, нет
Второй вопрос: отложить + восстановить Комбинацию, тогда я избавлюсь от нее. отличается Все в порядке? Следующим образом:
func main() {
if err := recover(); err != nil {
log.Printf("recover: %v", err)
}
panic("EDDYCJY.")
}Выходные результаты:
$ go run main.go
panic: EDDYCJY.
goroutine 1 [running]:
main.main()
/Users/eddycjy/go/src/github.com/EDDYCJY/awesomeProject/main.go:10 +0xa1
exit status 2Не совсем, ах, в конце концов, все вводные курсы написаны. отложить + восстановить Комбинация “всемогущий” захват. Но почему? Удалить отложить Тогда почему мы не можем его захватить?
Подумайте, почему вам нужно настроить его отложить после восстановления Может ли он работать?
В то же время вам нужно хорошенько подумать об этом. Давайте все устроим. отложить + восстановить Можете ли вы быть беззаботным после комбинации? Вы написали все виды “хаоса”?
4. Почему мы не можем запустить гороутину?
func main() {
go func() {
defer func() {
if err := recover(); err != nil {
log.Printf("recover: %v", err)
}
}()
}()
panic("EDDYCJY.")
}Выходные результаты:
$ go run main.go
panic: EDDYCJY.
goroutine 1 [running]:
main.main()
/Users/eddycjy/go/src/github.com/EDDYCJY/awesomeProject/main.go:14 +0x51
exit status 2Подумайте о том, почему у нас появился новый. Goroutine Разве вы не можете перехватывать исключения? Что случилось?
Исходный код
Затем мы начнем анализировать и анализировать исходный код с помощью вышеуказанных 4 + 1 небольших вопросов для размышлений, пытаясь найти ответы на вопросы для размышлений и многое другое, почему, прочитав исходный код.
структура данных
type _panic struct {
argp unsafe.Pointer
arg interface{}
link *_panic
recovered bool
aborted bool
}останься паника В использовании _паник Как его основная единица, он выполняется каждый раз. паника Заявление, создаст _panic . Оно содержит некоторые основные поля для хранения текущего паника В случае вызова используются следующие поля:
- Argp: указание
отложитьУказатель на параметры для отложенных вызовов - арг:
паникаПричина в звонке.паникаПараметры, прошедшие через время - Ссылка: ссылка на последний звонок
_panic - восстановлено:
паникаБыло ли оно обработано, то есть было ли оно обработано?восстановление - прервано:
паникаЭто приостановлено?
Кроме того, просмотрев ссылку Поля, вы можете увидеть, что это связанный список структуры данных, как показано ниже:
Паника
func main() {
panic("EDDYCJY.")
}Выходные результаты:
$ go run main.go
panic: EDDYCJY.
goroutine 1 [running]:
main.main()
/Users/eddycjy/go/src/github.com/EDDYCJY/awesomeProject/main.go:4 +0x39
exit status 2Давайте вернемся и проверим. паника Где иметь дело с конкретной логикой, заключается в следующем:
$ go tool compile -S main.go
"".main STEXT size=66 args=0x0 locals=0x18
0x0000 00000 (main.go:23) TEXT "".main(SB), ABIInternal, $24-0
0x0000 00000 (main.go:23) MOVQ (TLS), CX
0x0009 00009 (main.go:23) CMPQ SP, 16(CX)
...
0x002f 00047 (main.go:24) PCDATA $2, $0
0x002f 00047 (main.go:24) MOVQ AX, 8(SP)
0x0034 00052 (main.go:24) CALL runtime.gopanic(SB)Очевидно, что ассемблерный код указывает непосредственно на внутреннюю реализацию runtime.go panic Давайте посмотрим, что делает этот метод, следующим образом (опущенная часть):
func gopanic(e interface{}) {
gp := getg()
...
var p _panic
p.arg = e
p.link = gp._panic
gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
for {
d := gp._defer
if d == nil {
break
}
// defer...
...
d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
p.argp = unsafe.Pointer(getargp(0))
reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
p.argp = nil
// recover...
if p.recovered {
...
mcall(recovery)
throw("recovery failed") // mcall should not return
}
}
preprintpanics(gp._panic)
fatalpanic(gp._panic) // should not return
*(*int)(nil) = 0 // not reached
}- Получите указание на текущую
ПодпрограммуРуководящие принципы - Инициализировать
паникуБазовые блоки_panicИспользуется в качестве последующей операции - Получение текущей
ПодпрограммыЗагруженной_defer(Структура данных также является связанным списком) - Если он существует в настоящее время
отложитеВызов, затем вызовитеreflectcallМетод для выполнения предыдущегоотложитеКод, выполнение которого задерживается, если его необходимо запустить во время выполнениявосстановлениеВызоветgorecoverМетод - Перед закрытием используйте
предпечатную подготовкуЗадействованный метод печатипаникановости - Последний звонок
смертельная паникаПрервать заявку на самом деле означает выполнитьвыход(2)Проведение окончательного вывода средств
Благодаря анализу выполнения приведенного выше кода мы видим, что паника Этот метод на самом деле имеет дело с текущей ситуацией. Goroutine(g) Тот, который на нем ._panic Связанный список (поэтому больше ничего нельзя сделать) Goroutine Ответ на событие аномалии, а затем, к которому он принадлежит отложить Список ссылок и восстановить Для обнаружения и обработки и, наконец, вызвать команду выхода для завершения приложения
Неизлечимая паника, смертельная паника
func fatalpanic(msgs *_panic) {
pc := getcallerpc()
sp := getcallersp()
gp := getg()
var docrash bool
systemstack(func() {
if startpanic_m() && msgs != nil {
...
printpanics(msgs)
}
docrash = dopanic_m(gp, pc, sp)
})
systemstack(func() {
exit(2)
})
*(*int)(nil) = 0
}Мы видим, что этот метод будет выполнен в конце обработки исключений, и, похоже, он выполняет всю заключительную работу. На самом деле, это окончательное выполнение программы. выход Инструкции используются для остановки выполнения, но они проходят до завершения печатные панели Все сообщения об исключениях и параметры выводятся рекурсивно. Код выглядит следующим образом:
func printpanics(p *_panic) {
if p.link != nil {
printpanics(p.link)
print("\t")
}
print("panic: ")
printany(p.arg)
if p.recovered {
print(" [recovered]")
}
print("\n")
}Поэтому не предполагайте, что все исключения могут быть восстановлены , на самом деле, как фатальная ошибка и время выполнения.бросок Этого не может быть. восстановление Да, даже oom завершает программу напрямую, и какой-нибудь удар слева даст вам его. выход(2) Учите людей, как себя вести. Поэтому, когда вы пишете код, вам следует уделять больше внимания тому факту, что паника-это сценарий, который невозможно восстановить.
Восстановление
func main() {
defer func() {
if err := recover(); err != nil {
log.Printf("recover: %v", err)
}
}()
panic("EDDYCJY.")
}Выходные результаты:
$ go run main.go 2019/05/11 23:39:47 recover: EDDYCJY.
Как и ожидалось, исключение было успешно перехвачено. однако восстановление Как восстановиться паника Что с этим делать? Посмотрите на код сборки еще раз, как показано ниже:
$ go tool compile -S main.go
"".main STEXT size=110 args=0x0 locals=0x18
0x0000 00000 (main.go:5) TEXT "".main(SB), ABIInternal, $24-0
...
0x0024 00036 (main.go:6) LEAQ "".main.func1·f(SB), AX
0x002b 00043 (main.go:6) PCDATA $2, $0
0x002b 00043 (main.go:6) MOVQ AX, 8(SP)
0x0030 00048 (main.go:6) CALL runtime.deferproc(SB)
...
0x0050 00080 (main.go:12) CALL runtime.gopanic(SB)
0x0055 00085 (main.go:12) UNDEF
0x0057 00087 (main.go:6) XCHGL AX, AX
0x0058 00088 (main.go:6) CALL runtime.deferreturn(SB)
...
0x0022 00034 (main.go:7) MOVQ AX, (SP)
0x0026 00038 (main.go:7) CALL runtime.gorecover(SB)
0x002b 00043 (main.go:7) PCDATA $2, $1
0x002b 00043 (main.go:7) MOVQ 16(SP), AX
0x0030 00048 (main.go:7) MOVQ 8(SP), CX
...
0x0056 00086 (main.go:8) LEAQ go.string."recover: %v"(SB), AX
...
0x0086 00134 (main.go:8) CALL log.Printf(SB)
...Анализируя базовые вызовы, мы видим, что основные методы заключаются в следующем:
- Анализируя базовые вызовы, мы видим, что основные методы заключаются в следующем:
- Анализируя базовые вызовы, мы видим, что основные методы заключаются в следующем:
- Анализируя базовые вызовы, мы видим, что основные методы заключаются в следующем:
- Анализируя базовые вызовы, мы видим, что основные методы заключаются в следующем:
В последнем разделе мы описали простой процесс. перейти в панику Метод вызывает текущую Подпрограмму Понизить отложить Список ссылок, если reflectcall Обнаружен при выполнении восстановление Он будет называться gorecover Код этого метода выглядит следующим образом:
func gorecover(argp uintptr) interface{} {
gp := getg()
p := gp._panic
if p != nil && !p.recovered && argp == uintptr(p.argp) {
p.recovered = true
return p.arg
}
return nil
}Этот код, кажется, очень прост, суть в том, чтобы его модифицировать. восстановленные Поля. Это поле используется для определения текущей паники Была ли она восстановлена Обработана. Но это не то же самое, что мы себе представляли. Как работает программа? паника А как насчет тех, кто возвращается? Рассматривался ли он в рамках основного подхода? Давайте посмотрим еще раз. начинай паниковать Код выглядит следующим образом:
func gopanic(e interface{}) {
...
for {
// defer...
...
pc := d.pc
sp := unsafe.Pointer(d.sp) // must be pointer so it gets adjusted during stack copy
freedefer(d)
// recover...
if p.recovered {
atomic.Xadd(&runningPanicDefers, -1)
gp._panic = p.link
for gp._panic != nil && gp._panic.aborted {
gp._panic = gp._panic.link
}
if gp._panic == nil {
gp.sig = 0
}
gp.sigcode0 = uintptr(sp)
gp.sigcode1 = pc
mcall(recovery)
throw("recovery failed")
}
}
...
}Давай вернемся давай паниковать Более пристальный взгляд на метод показывает, что он на самом деле содержит пару восстановления Обработки кода для потока. Процесс восстановления выглядит следующим образом:
- Судя по настоящему
_panicНосительвосстановленБыл ли он помечен как обработанный? - из
_panicУдалить аннотированные завершенные элементы из спискапаникаСобытия, т. е. удаления, которые были восстановленыпаникаСобытие - Передайте информацию о кадре стека, которую необходимо восстановить, в
восстановлениеМетодическиеgpПараметры (каждый кадр стека соответствует незавершенной функции. Обратный адрес и локальные переменные функции сохраняются во фрейме стека. - осуществить
восстановлениеПровести восстановительные действия
С точки зрения процесса, ядром является метод восстановления . Он берет на себя ответственность за управление ненормальным потоком. Код выглядит следующим образом:
func recovery(gp *g) {
sp := gp.sigcode0
pc := gp.sigcode1
if sp != 0 && (sp < gp.stack.lo || gp.stack.hi < sp) {
print("recover: ", hex(sp), " not in [", hex(gp.stack.lo), ", ", hex(gp.stack.hi), "]\n")
throw("bad recovery")
}
gp.sched.sp = sp
gp.sched.pc = pc
gp.sched.lr = 0
gp.sched.ret = 1
gogo(&gp.sched)
}На первый взгляд кажется, что некоторые ценности были установлены очень просто. Но на самом деле задается значение псевдорегистратора в компиляторе, которое часто используется для поддержания контекста и так далее. Здесь нам нужно объединить гопанический Метод наблюдения Вместе восстановление Метод. Указатель стека, который он использует sp И счетчик программ pc По настоящему отложить В процессе вызова отложить Это передается по наследству, так что на самом деле это наконец-то прошло. gogo Метод отскочил назад deferproc Метод. Кроме того, мы отмечаем, что:
gp.sched.ret = 1
В нижней части программа gp.sched.ret Установит значение 1, то есть Не будет фактического вызова | Метода deferproc , напрямую изменяя его возвращаемое значение. Это означает, что по умолчанию он был обработан. Прямая передача отсрочка Следующая инструкция метода идет. До сих пор управление потоком в ненормальном состоянии закончилось. Следующий шаг-идти дальше. отложить Процесс
Чтобы проверить эту идею, мы можем взглянуть на основной метод прыжка. gogo Код выглядит следующим образом:
// void gogo(Gobuf*)
// restore state from Gobuf; longjmp
TEXT runtime·gogo(SB),NOSPLIT,$8-4
MOVW buf+0(FP), R1
MOVW gobuf_g(R1), R0
BL setg<>(SB)
MOVW gobuf_sp(R1), R13 // restore SP==R13
MOVW gobuf_lr(R1), LR
MOVW gobuf_ret(R1), R0
MOVW gobuf_ctxt(R1), R7
MOVW $0, R11
MOVW R11, gobuf_sp(R1) // clear to help garbage collector
MOVW R11, gobuf_ret(R1)
MOVW R11, gobuf_lr(R1)
MOVW R11, gobuf_ctxt(R1)
MOVW gobuf_pc(R1), R11
CMP R11, R11 // set condition codes for == test, needed by stack split
B (R11)Посмотрев на код, вы можете увидеть, что его основная функция находится в Gobuf Восстановление. Проще говоря, значение регистра изменяется в соответствии с ним. Goroutine(g) Значение значения, которое много раз обсуждалось в этой статье. Гобуф Следующим образом:
type gobuf struct {
sp uintptr
pc uintptr
g guintptr
ctxt unsafe.Pointer
ret sys.Uintreg
lr uintptr
bp uintptr
}Разумно, на самом деле, то, что он хранит Goroutine Некоторые вещи, которые вам нужны для переключения контекста
Расширять
const(
OPANIC // panic(Left)
ORECOVER // recover()
...
)
...
func walkexpr(n *Node, init *Nodes) *Node {
...
switch n.Op {
default:
Dump("walk", n)
Fatalf("walkexpr: switch 1 unknown op %+S", n)
case ONONAME, OINDREGSP, OEMPTY, OGETG:
case OTYPE, ONAME, OLITERAL:
...
case OPANIC:
n = mkcall("gopanic", nil, init, n.Left)
case ORECOVER:
n = mkcall("gorecover", n.Type, init, nod(OADDR, nodfp, nil))
...
}Фактически вызывая панику и восстановление , когда ключевые слова переводятся в соответствующий код операции на этапе компиляции, компилятор переводит их в соответствующие методы выполнения. Это не так, как ты думаешь, это один шаг за раз. Заинтересованные партнеры могут изучить его.
резюме
Эта статья в основном направлена на панику и восстановление Глубокий анализ исходного кода по ключевым словам, а первые 4 + 1 мыслящих вопроса-это надежда на то, что вы сможете изучить вопросы, достичь вдвое большего результата с половиной усилий.
Кроме того, эта статья и отложите Есть определенная актуальность, поэтому нам нужны некоторые базовые знания. Если вы не поняли эту часть, когда только что прочитали ее, вы можете прочитать ее снова после изучения, чтобы углубить свое впечатление.
В конце концов, можете ли вы ответить на эти вопросы сейчас? Высказаться-это действительно понять:)