延宕執行,妙用無窮,Go lang1.18入門精煉教程,由白丁入鴻儒,Golang中defer關鍵字延遲調用機制使用EP17

来源:https://www.cnblogs.com/v3ucn/archive/2022/09/07/16665066.html
-Advertisement-
Play Games

先行定義,延後執行。不得不佩服Go lang設計者天才的設計,事實上,defer關鍵字就相當於Python中的try{ ...}except{ ...}finally{...}結構設計中的finally語法塊,函數結束時強制執行的代碼邏輯,但是defer在語法結構上更加優雅,在函數退出前統一執行,可 ...


先行定義,延後執行。不得不佩服Go lang設計者天才的設計,事實上,defer關鍵字就相當於Python中的try{ ...}except{ ...}finally{...}結構設計中的finally語法塊,函數結束時強制執行的代碼邏輯,但是defer在語法結構上更加優雅,在函數退出前統一執行,可以隨時增加defer語句,多用於系統資源的釋放以及相關善後工作。當然了,這種流程結構是必須的,形式上可以不同,但底層原理是類似的,Golang 選擇了更簡約的defer,避免多級嵌套的try except finally 結構。

使用場景

操作系統資源在業務上避免不了的,比方說單例對象的使用權、文件讀寫、資料庫讀寫、鎖的獲取和釋放等等,這些資源需要在使用完之後釋放掉或者銷毀,如果忘記釋放、資源會常駐記憶體,長此以往就會造成記憶體泄漏的問題。但是人非聖賢,孰能無過?因此研發者在撰寫業務的時候有幾率忘記關閉這些資源。

Golang中defer關鍵字的優勢在於,在打開資源語句的下一行,就可以直接用defer語句來註冊函數結束後執行關閉資源的操作。說白了就是給程式邏輯“上鬧鐘”,定義好邏輯結束時需要關閉什麼資源,如此,就降低了忘記關閉資源的概率:

package main  
  
import (  
	"fmt"  
	"github.com/jinzhu/gorm"  
	_ "github.com/jinzhu/gorm/dialects/mysql"  
)  
  
func main() {  
	db, err := gorm.Open("mysql", "root:root@(localhost)/mytest?charset=utf8mb4&parseTime=True&loc=Local")  
  
	if err != nil {  
                fmt.Println(err)  
       		fmt.Println("連接資料庫出錯")  
		return  
	}  
  
	defer db.Close()  
        fmt.Println("鏈接Mysql成功")  
  
}

這裡通過gorm獲取資料庫指針變數後,在業務開始之前就使用defer定義好資料庫鏈接的關閉,在main函數執行完畢之前,執行db.Close()方法,所以列印語句是在defer之前執行的。

所以需要註意的是,defer最好在業務前面定義,如果在業務後面定義:

fmt.Println("鏈接Mysql成功")  
defer db.Close()

這樣寫就是畫蛇添足了,因為本來就是結束前執行,這裡再加個defer關鍵字的意義就不大了,反而會在編譯的時候增加程式的判斷邏輯,得不償失。

defer執行順序問題

Golang並不會限制defer關鍵字的數量,一個函數中允許多個“延遲任務”:

package main  
  
import "fmt"  
  
func main() {  
	defer func1()  
	defer func2()  
	defer func3()  
}  
  
func func1() {  
	fmt.Println("任務1")  
}  
  
func func2() {  
	fmt.Println("任務2")  
}  
  
func func3() {  
	fmt.Println("任務3")  
}

程式返回:

任務3  
任務2  
任務1

我們可以看到,多個defer的執行順序其實是“反”著的,先定義的後執行,後定義的先執行,為什麼?因為defer的執行邏輯其實是一種“壓棧”行為:

package main  
  
import (  
	"fmt"  
	"sync"  
)  
  
// Item the type of the stack  
type Item interface{}  
  
// ItemStack the stack of Items  
type ItemStack struct {  
	items []Item  
	lock  sync.RWMutex  
}  
  
// New creates a new ItemStack  
func NewStack() *ItemStack {  
	s := &ItemStack{}  
	s.items = []Item{}  
	return s  
}  
  
// Pirnt prints all the elements  
func (s *ItemStack) Print() {  
	fmt.Println(s.items)  
}  
  
// Push adds an Item to the top of the stack  
func (s *ItemStack) Push(t Item) {  
	s.lock.Lock()  
	s.lock.Unlock()  
	s.items = append(s.items, t)  
}  
  
// Pop removes an Item from the top of the stack  
func (s *ItemStack) Pop() Item {  
	s.lock.Lock()  
	defer s.lock.Unlock()  
	if len(s.items) == 0 {  
		return nil  
	}  
	item := s.items[len(s.items)-1]  
	s.items = s.items[0 : len(s.items)-1]  
	return item  
}

這裡我們使用切片和結構體實現了棧的數據結構,當元素入棧的時候,會進入棧底,後進的會把先進的壓住,出棧則是後進的先出:

func main() {  
  
	var stack *ItemStack  
	stack = NewStack()  
	stack.Push("任務1")  
	stack.Push("任務2")  
	stack.Push("任務3")  
	fmt.Println(stack.Pop())  
	fmt.Println(stack.Pop())  
	fmt.Println(stack.Pop())  
  
}

程式返回:

任務3  
任務2  
任務1

所以,在defer執行順序中,業務上需要先執行的一定要後定義,而業務上後執行的一定要先定義。

除此以外,就是與其他執行關鍵字的執行順序問題,比方說return關鍵字:

package main  
  
import "fmt"  
  
func main() {  
	test()  
}  
  
func test() string {  
	defer fmt.Println("延時任務執行")  
	return testRet()  
}  
  
func testRet() string {  
	fmt.Println("返回值函數執行")  
	return ""  
}

程式返回:

返回值函數執行  
延時任務執行

一般情況下,我們會認為return就是結束邏輯,所以return邏輯應該會最後執行,但實際上defer會在retrun後面執行,所以defer中的邏輯如果依賴return中的執行結果,那麼就絕對不能使用defer關鍵字。

業務與特性結合

我們知道,有些內置關鍵字不僅僅具備表層含義,如果瞭解其特性,是可以參與業務邏輯的,比如說Python中的try{ ...}except{ ...}finally{...}結構,錶面上是捕獲異常,輸出異常,其實可以利用其特性搭配唯一索引,就可以直接完成排重業務,從而減少一次磁碟的IO操作。

defer也如此,假設我們要在同一個函數中打開不同的文件進行操作:

package main  
  
import (  
	"os"  
)  
  
func mergeFile() error {  
  
	f1, _ := os.Open("file1.txt")  
	if f1 != nil {  
  
		//操作文件  
		f1.Close()  
	}  
  
	f2, _ := os.Open("file2.txt")  
	if f2 != nil {  
  
		//操作文件  
		f2.Close()  
	}  
  
	return nil  
}  
  
func main(){  
mergeFile()  
}

所以理論上,需要兩個文件句柄對象,分別打開不同的文件,然後同步執行。

但讓defer關鍵字參與進來:

package main  
  
import (  
	"fmt"  
	"io"  
	"os"  
)  
  
func mergeFile() error {  
  
	f, _ := os.Open("file1.txt")  
	if f != nil {  
		defer func(f io.Closer) {  
			if err := f.Close(); err != nil {  
				fmt.Printf("文件1關閉 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("文件2關閉 err err %v\n", err)  
			}  
		}(f)  
	}  
  
	return nil  
}  
  
func main() {  
  
	mergeFile()  
}

這裡就用到了defer的特性,defer函數定義的時候,句柄參數就已經複製進去了,隨後,真正執行close()函數的時候就剛好關閉的是對應的文件了,如此,同一個句柄對不同文件進行了復用,我們就節省了一次記憶體空間的分配。

defer一定會執行嗎

我們知道Python中的try{ ...}except{ ...}finally{...}結構,finally僅僅是理論上會執行,一旦遇到特殊情況:

from peewee import MySQLDatabase  
  
class Db:  
  
    def __init__(self):  
  
        self.db = MySQLDatabase('mytest', user='root', password='root',host='localhost', port=3306)  
  
    def __enter__(self):  
        print("connect")  
        self.db.connect()  
        exit(-1)  
  
    def __exit__(self,*args):  
        print("close")  
        self.db.close()  
  
with Db() as db:  
    print("db is opening")

程式返回:

connect

並未執行print("db is opening")邏輯,是因為在__enter__方法中就已經結束了(exit(-1))

而defer同理:

package main  
  
import (  
	"fmt"  
	"os"  
)  
  
func main() {  
	defer func() {  
		fmt.Printf("延後執行")  
	}()  
	os.Exit(1)  
}

這裡和Python一樣,同樣調用os包中的Exit函數,程式返回:

exit status 1

延遲方法並未執行,所以defer並非一定會執行。

結語

defer關鍵字是極其天才的設計,業務簡單的情況下不會有什麼問題。但也需要深入理解defer的特性以及和其他內置關鍵字的關係,才能發揮它最大的威力,著名語言C#最新版本支持了 using無括弧的形式,預設當前塊結束時釋放資源,這也算是對defer關鍵字的一種致敬罷。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.什麼是模板層 模板層可以根據視圖中傳遞的字典數據動態生產相應的HTML頁面 2.模板層的配置 1.在項目下創建一個與同名文件夾平行的templates文件夾 2.在settings.py中的TEMPLATES配置項中 BACKEND:指定模板的引擎 DIRS:模板的搜索目錄(可以是一個或者多個) ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 最近在研究一個基於TP6的框架CRMEB,這裡分享下我的開發心得 首先要獲取原始項目文件 這裡是git地址 https://gitee.com/ZhongBangKeJi/CRMEB.git 項目環境的要求為Apache、MySQL、PH ...
  • 命令版 示例: 將main分支轉到master分支上 切到需要使用的分支 git checkout master 強制忽略歷史融合 git merge main --allow-unrelated-histories 3.提交融合衝突文件即可 ...
  • Django_ajax 1 簡介 AJAX(Asynchronous Javascript And XML)翻譯成中文就是“非同步Javascript和XML”。即使用Javascript語言與伺服器進行非同步交互,傳輸的數據為XML(當然,傳輸的數據不只是XML)。 同步交互:客戶端發出一個請求後,需 ...
  • 作用域 作用域分為: 全局作用域 局部作用域 在函數內部的作用域叫做局部作用域,局部作用域中的變數叫做局部變數 非函數內部的作用域叫做全局作用域,全局作用域中的變數叫做全局變數 局部作用域可以使用全局變數,全局變數不能試用局部變數 變數的使用規則: 從內向外,找到後返回 函數作用域中命名全局變數gl ...
  • 我們上面使用swarm部署服務,單個服務還好,如果很多個服務怎麼來解決呢,這裡就用到了Docker Stack管理服務。 ​ 在上面我們學會瞭如何配置一個swarm集群,並且知道如何在swarm集群上部署應用,現在,我們開始瞭解Docker層級關係中的最高一個層級——stack。一個stack就是一 ...
  • “如何解決TCC中的懸掛問題”! 一個工作了4年的Java程式員,去京東面試,被問到這個問題。 大家好,我是Mic,一個工作了14年的Java程式員 這個問題面試官想考察什麼方面的知識?我們又該怎麼回答呢? 問題解析 TCC是分散式事務問題裡面的解決方案,一般在應聘互聯網公司的時候問的比較多。 實際 ...
  • 1.什麼是視圖層 簡單來說,就是用來接收路由層傳來的請求,從而做出相應的響應返回給瀏覽器 2.視圖層的格式與參數說明 2.1基本格式 from django.http import HttpResponse def page_2003(request): html = '<h1>第一個網頁</h1> ...
一周排行
    -Advertisement-
    Play Games
  • 概述:這個WPF項目通過XAML繪製汽車動態速度表盤,實現了0-300的速度刻度,包括數字、指針,並通過定時器模擬速度變化,展示了動態效果。詳細實現包括界面設計、刻度繪製、指針角度計算等,通過C#代碼與XAML文件結合完成。 新建 WPF 項目: 在 Visual Studio 中創建一個新的 WP ...
  • 概述:在WPF中使用`WpfAnimatedGif`庫展示GIF動畫,首先確保全裝了該庫。通過XAML設置Image控制項,指定GIF路徑,然後在代碼中使用庫提供的方法實現動畫控制。這簡化了在WPF應用中處理GIF圖的過程,提供了方便的介面來管理動畫播放和暫停。 當使用 WpfAnimatedGif  ...
  • 您是否曾經訪問過一個網站,它需要很長時間載入,最終你敲擊 F5 重新載入頁面。 即使用戶刷新了瀏覽器取消了原始請求,而對於伺服器來說,API也不會知道它正在計算的值將在結束時被丟棄,刷新五次,伺服器將觸發 5 個請求。 為瞭解決這個問題,ASP.NET Core 為 Web 伺服器提供了一種機制,就 ...
  • 本章將和大家分享如何通過 Elasticsearch 實現自動補全查詢功能。 一、自動補全-安裝拼音分詞器 1、自動補全需求說明 當用戶在搜索框輸入字元時,我們應該提示出與該字元有關的搜索項,如圖: 2、使用拼音分詞 要實現根據字母做補全,就必須對文檔按照拼音分詞。在 GitHub 上恰好有 Ela ...
  • using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Windows.Forms; namespace OOP { pub ...
  • 概述:以上內容詳細介紹了在C#中如何從另一個線程更新GUI,包括基礎功能和高級功能。對於WinForms,使用`Control.Invoke`;對於WPF,使用`Dispatcher.Invoke`。高級功能使用`SynchronizationContext`實現線程間通信,確保清晰、可讀性高的代碼 ...
  • Nuget包 Microsoft.Extensions.Telemetry.Abstractions 包含的新的日誌記錄source generator,它支持使用[LogProperties]將整個對象作為State與日誌一起記錄。 我將展示一種方法來控制如何使用[LogProperties]對象 ...
  • 支持.Net/.Net Core/.Net Framework,可以部署在Docker, Windows, Linux, Mac。 常見的ORM技術(比如:Entity Framework,Dapper,SqlSugar,NHibernate,等…),它們不是在做Sql語句的程式化變種,就是在做Sq ...
  • 一、引言 在現代應用程式開發中,尤其是在涉及I/O操作(如網路請求、文件讀寫等)時,非同步編程成為了提高性能和用戶體驗的關鍵技術。C#作為.NET框架下的主流開發語言,提供了強大的非同步編程支持,通過async/await關鍵字,可以讓開發者以同步的方式編寫非同步代碼,極大地簡化了非同步編程的複雜性。本文將 ...
  • 一、引言 在.NET開發中,操作Office文檔(特別是Excel和Word)是一項常見的需求。然而,在伺服器端或無Microsoft Office環境的場景下,直接使用Office Interop可能會面臨挑戰。為瞭解決這個問題,開源庫NPOI應運而生,它提供了無需安裝Office即可創建、讀取和 ...