Рубрики
Uncategorized

Глубокое понимание того, как откладывать

Автор оригинала: David Wong.

В предыдущей главе, Понимая Панику и восстановление, мы обнаружили, что откладываем С ее большой актуальностью или считаем очень необходимым углубиться. Я надеюсь, что в этой главе вы сможете отложить Ключевое слово имеет глубокое понимание, поэтому давайте начнем. Подожди минутку. Пожалуйста, постройтесь. Мы воспользуемся режимом выхода 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
  • голанг -примечания