Простое решение Голанга для нежной ловушки отсрочки

Go

Что такое отсрочка?

deferЭто механизм, предоставляемый языком Go для регистрации отложенных вызовов: выполнение функций или операторов после выполнения текущей функции (включая нормальное завершение возврата или аварийное завершение, вызванное паникой).

deferОператоры обычно используются в некоторых парных сценариях работы: открыть соединение/закрыть соединение, заблокировать/снять блокировку, открыть файл/закрыть файл и т. д.

deferЭто очень полезно в некоторых сценариях, требующих повторного использования ресурсов, и удобно выполнять некоторые операции очистки перед завершением функции. В следующей строке оператора open resource прямая отсрочка может закрыть ресурс до возврата из функции, что весьма элегантно.

f, _ := os.Open("defer.txt")
defer f.Close()

Примечание. Приведенный выше код игнорирует ошибку. Фактически, вы должны сначала определить, есть ли ошибка. Если есть ошибка, вернитесь напрямую. Затем оценитеfпусто, еслиfЕсли он пуст, его нельзя вызватьf.Close()функции, он будет паниковать напрямую.

Зачем нужна отсрочка?

Когда программисты программируют, им часто нужно открыть некоторые ресурсы, такие как соединения с базой данных, файлы, блокировки и т. д. Эти ресурсы необходимо освобождать после того, как они будут израсходованы, иначе это вызовет утечку памяти.

Но программисты тоже люди, а люди ошибаются. Поэтому часто программисты забывают закрыть эти ресурсы. Golang обеспечивает прямо на уровне языкаdeferключевое слово, в следующей строке оператора открытого ресурса вы можете использовать его напрямуюdeferоператор для регистрации операции закрытия ресурса после завершения функции. Из-за такого «небольшого» синтаксического сахара число программистов, забывающих писать операторы close resource, значительно снижается.

Как разумно использовать отсрочку?

Использование defer на самом деле очень простое:

f,err := os.Open(filename)
if err != nil {
    panic(err)
}

if f != nil {
    defer f.Close()
}

Закройте файл с помощью оператора defer рядом с оператором, открывающим файл. Таким образом, перед завершением функции автоматически выполняется оператор, следующий за defer, чтобы закрыть файл.

Конечно, defer будет иметь небольшую задержку, и программы с особенно высокими требованиями к времени могут его не использовать, а другие вообще игнорируют вызванную им задержку.

расширенная отсрочка

В чем заключается принцип отсрочки?

Давайте посмотрим на официальнуюdeferобъяснение:

Каждый раз, когда выполняется оператор «отложить», значение функции и параметры вызова оцениваются, как обычно, и сохраняются заново, но фактическая функция не вызывается. Вместо этого отложенные функции вызываются непосредственно перед возвратом из окружающей функции в обратном порядке. Если значение отложенной функции оценивается как nil, выполнение прерывается при вызове функции, а не при выполнении оператора «отложить».

Перевод: каждый раз, когда выполняется оператор отсрочки, функция будет «заталкиваться» в стек, а параметры функции будут скопированы; когда внешняя функция (не кодовый блок, такой как цикл for) выходит, функция отсрочки выполняется в порядке, обратном определенному ; Если функция, выполняемая defer, равна нулю, при последнем вызове функции произойдет паника.

Оператор defer не будет выполняться немедленно, а войдет в стек.Перед тем, как функция вернется, она будет выполнена в порядке «первым пришел, последним вышел». Это также означает, что оператор defer, определенный первым, выполняется последним. Причина принципа «первым пришел, последним вышел» состоит в том, что функции, определенные позже, могут зависеть от предыдущих ресурсов и, естественно, должны выполняться первыми; в противном случае, если предыдущие функции выполняются первыми, зависимости последних функций исчезнут.

При определении функции отсрочки есть два способа ссылки на внешние переменные: как параметры функции и как ссылки замыкания. В качестве параметра функции значение передается в defer, когда defer определен и кэшируется; в качестве ссылки на замыкание текущее значение будет определяться в соответствии со всем контекстом, когда функция defer действительно вызывается.

При выполнении инструкции после отсрочки параметры вызова функции будут сохранены, то есть копия. При фактическом выполнении скопированная переменная фактически используется, поэтому, если эта переменная является «значением», она такая же, как и при ее определении. Если переменная является "ссылкой", она может быть не такой, какой она была при определении.

Например:

func main() {
	var whatever [3]struct{}
	
	for i := range whatever {
		defer func() { 
			fmt.Println(i) 
		}()
	}
}

Результаты:

2
2
2

За defer следует замыкание (подробнее об этом позже), i — это переменная типа «ссылка», и, наконец, i имеет значение 2, поэтому в конце печатаются три двойки.

Опираясь на вышеизложенное, давайте посмотрим, какие результаты:

type number int

func (n number) print()   { fmt.Println(n) }
func (n *number) pprint() { fmt.Println(*n) }

func main() {
	var n number

	defer n.print()
	defer n.pprint()
	defer func() { n.print() }()
	defer func() { n.pprint() }()

	n = 3
}

Результат выполнения:

3
3
3
0

Четвертый оператор defer — это замыкание, которое ссылается на n внешней функции, а окончательный результат равен 3; Третий оператор defer аналогичен четвертому; Второй оператор defer, где n — ссылка, в конечном итоге оценивается как 3. Первый оператор defer, который вычисляет n напрямую, начинается с n=0, поэтому он заканчивается на 0;

Используйте принцип отсрочки

В некоторых случаях мы намеренно будем использовать сначала оценку defer, а затем характер отложенного вызова. Представьте себе такой сценарий: в функции вам нужно открыть два файла для операции слияния, после слияния закрыть дескриптор открытого файла после выполнения функции.

func mergeFile() error {
	f, _ := os.Open("file1.txt")
	if f != nil {
		defer func(f io.Closer) {
			if err := f.Close(); err != nil {
				fmt.Printf("defer close file1.txt err %v\n", err)
			}
		}(f)
	}

	// ……

	f, _ = os.Open("file2.txt")
	if f != nil {
		defer func(f io.Closer) {
			if err := f.Close(); err != nil {
				fmt.Printf("defer close file2.txt err %v\n", err)
			}
		}(f)
	}

	return nil
}

В приведенном выше коде используется принцип отсрочки: когда функция отсрочки определена, параметры копируются. Можно представить, что если f не передается в качестве параметра функции таким образом, последние два оператора закроют один и тот же файл, который является последним открытым файлом.

Однако при вызове функции close() обратите внимание на одну вещь: сначала оцените, пусто ли вызывающее тело, иначе произойдет паника.Например, в приведенном выше фрагменте кода сначала оценитеfне пустой, он будет называтьсяClose()функция, которая является самой безопасной.

Разборка команды defer

Если бы defer был таким простым, как описано выше (а это не так просто), мир был бы идеальным. Не всегда все так просто: если defer использовать неправильно, он попадет во множество ям.

Ключом к пониманию этих ям является следующее утверждение:

return xxx

После компиляции приведенного выше оператора он становится тремя инструкциями:

1. 返回值 = xxx
2. 调用defer函数
3. 空的return

Шаги 1 и 3 — это настоящие команды оператора Return, а шаг 2 — это оператор, определенный defer, где можно манипулировать возвращаемым значением.

Давайте рассмотрим два примера и попробуем разбить оператор return и оператор defer в правильном порядке.

Первый пример:

func f() (r int) {
     t := 5
     defer func() {
       t = t + 5
     }()
     return t
}

После разборки:

func f() (r int) {
     t := 5
     
     // 1. 赋值指令
     r = t
     
     // 2. defer被插入到赋值与返回之间执行,这个例子中返回值r没被修改过
     func() {        
         t = t + 5
     }
     
     // 3. 空的return指令
     return
}

На втором шаге операция не возвращает значение r, поэтому f() вызывается в основной функции для получения 5.

Второй пример:

func f() (r int) {
    defer func(r int) {
          r = r + 5
    }(r)
    return 1
}

После разборки:

func f() (r int) {
     // 1. 赋值
     r = 1
     
     // 2. 这里改的r是之前传值传进去的r,不会改变要返回的那个r值
     func(r int) { 
          r = r + 5
     }(r)
     
     // 3. 空的return
     return
}

Следовательно, вызов f() в основной функции получает 1.

Параметры для отложенных утверждений

Выражение оператора отсрочки значения в определении было идентифицировано. Ниже показаны три функции:

func f1() {
	var err error
	
	defer fmt.Println(err)

	err = errors.New("defer error")
	return
}

func f2() {
	var err error
	
	defer func() {
		fmt.Println(err)
	}()

	err = errors.New("defer error")
	return
}

func f3() {
	var err error
	
	defer func(err error) {
		fmt.Println(err)
	}(err)

	err = errors.New("defer error")
	return
}

func main() {
	f1()
	f2()
	f3()
}

результат операции:

<nil>
defer error
<nil>

Первая и третья функции используются как параметры функции, и они будут оцениваться, когда будут определены.Когда они определены, значение переменной err равно нулю, поэтому все они равны нулю, когда они будут окончательно напечатаны.Параметры функции вторая функция также будет в. Она оценивается, когда она определена, за исключением того, что во втором примере это замыкание, и переменная err, на которую она ссылается, в конечном итоге становится при ее выполнении.defer error. Замыкания представлены далее в этой статье.

Ошибку третьей функции совершить относительно легко, в продакшене легко написать такой неверный код. Последний оператор отсрочки не сработал.

Что такое закрытие?

Замыкание — это сущность, состоящая из функции и связанной с ней эталонной среды, а именно:

闭包=函数+引用环境

Обычные функции имеют имена функций, а анонимные функции — нет. Анонимная функция не может существовать независимо, но может быть вызвана напрямую или присвоена переменной. Анонимные функции также известны как замыкания, замыкание наследует область действия объявления функции. В Golang все анонимные функции являются замыканиями.

В качестве менее подходящего примера замыкание можно рассматривать как класс, а вызов функции замыкания — это экземпляр класса. Закрытие может иметь несколько экземпляров во время выполнения, оно фиксирует переменные и константы в одной и той же области видимости, и эти переменные и константы можно использовать независимо от того, где вызывается замыкание (создается экземпляр). Кроме того, переменные и константы, захваченные замыканиями, передаются по ссылке, а не по значению.

Возьмем простой пример:

func main() {
	var a = Accumulator()

	fmt.Printf("%d\n", a(1))
	fmt.Printf("%d\n", a(10))
	fmt.Printf("%d\n", a(100))

	fmt.Println("------------------------")
	var b = Accumulator()

	fmt.Printf("%d\n", b(1))
	fmt.Printf("%d\n", b(10))
	fmt.Printf("%d\n", b(100))


}

func Accumulator() func(int) int {
	var x int

	return func(delta int) int {
		fmt.Printf("(%+v, %+v) - ", &x, x)
		x += delta
		return x
	}
}

Результаты:

(0xc420014070, 0) - 1
(0xc420014070, 1) - 11
(0xc420014070, 11) - 111
------------------------
(0xc4200140b8, 0) - 1
(0xc4200140b8, 1) - 11
(0xc4200140b8, 11) - 111

Замыкание относится к переменной x, a и b можно рассматривать как два разных экземпляра, и экземпляры не влияют друг на друга. Внутри экземпляра переменная x имеет тот же адрес и, следовательно, имеет «аддитивный эффект».

отложить с восстановлением

Golang больше критикуют за его ошибки, которые часто наполнены различными ошибками. При программировании всегда возвращается ошибка, с которой остается разобраться вызывающей стороне. Если это фатальная ошибка, например проблема при инициализации программы, просто паникуйте, чтобы избежать более серьезных проблем после подключения к сети.

但是有些时候,我们需要从异常中恢复。 For example, the server program encounters a serious problem and panic occurs. At this time, we can at least do some "finishing work" before the program crashes, such as closing the connection of the client, preventing the client from waiting, and so на.

ПАНИКА остановит текущую программу, а не только текущую цену. До этого он выполняет оператор в текущем списке отсрочки соглашения, и зависание оператора DEFER в другие недели не гарантируется. Поэтому мы часто вешаем оператор Recover в Defer, предотвращая зависание программы напрямую, что былоtry...catchЭффект.

Обратите внимание, что функция recovery () действительна только в контексте отсрочки (и действительна только при вызове ее с анонимной функцией в отсрочке), если она вызывается напрямую, она вернет толькоnil.

func main() {
	defer fmt.Println("defer main")
	var user = os.Getenv("USER_")
	
	go func() {
		defer func() {
			fmt.Println("defer caller")
			if err := recover(); err != nil {
				fmt.Println("recover success. err: ", err)
			}
		}()

		func() {
			defer func() {
				fmt.Println("defer here")
			}()

			if user == "" {
				panic("should set user env.")
			}

			// 此处不会执行
			fmt.Println("after panic")
		}()
	}()

	time.Sleep(100)
	fmt.Println("end of main function")
}

Вышеупомянутая паника в конечном итоге будет захвачена восстановлением. Этот метод обработки часто используется в основном процессе http-сервера. Случайный запрос может вызвать ошибку, а затем используйте команду восстановления для захвата паники, чтобы стабилизировать основной процесс, не затрагивая другие запросы.

Программист узнает о возникновении этой паники посредством мониторинга, находит соответствующую позицию журнала в соответствии с моментом времени, находит причину паники, делит два на три и исправляет ее в режиме онлайн. Оглядываясь по сторонам, каждый погружен в свое дело, что идеально: тайно исправил баг, но так и не нашел! Эй-эй!

постскриптум

Отсрочка очень проста в использовании, при нормальных обстоятельствах проблем не возникнет. Но только глубокое понимание принципов позволит избежать нежной ловушки отсрочки. Вооружившись своими принципами, он напишет код, который будет легко понять и поддерживать.

QR

использованная литература

[отложить эти вещи]xiaozhou.net/something-ah...[отложить регистр кода]Genius cat.git books.IO/go-internal…【Закрытие】Уууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууууу Пэнцзе…【Закрытие】blog.51CTO.com/speaking white…【Закрытие】blog.CSDN.net/Опираясь на трагедию...【Задерживать】Ли Янлян What/posts/2014/…【Три принципа отсрочки】О, управление оружием.GitHub.IO/2016/10/15/…[отложить пример кода]nuggets.capable/post/684490…【отложить панику】IE накануне E.com/specialties/2017/1…【отложить панику】zhuanlan.zhihu.com/p/33743255