延宕執行,妙用無窮,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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...