重慶分公司,新征程啟航
為企業提供網站建設、域名注冊、服務器等服務
為企業提供網站建設、域名注冊、服務器等服務
在以下這段代碼中,我們操作一個文件,無論成功與否都需要關閉文件句柄。這里在三處不同的位置都調用了file.Close()方法,代碼顯得非常冗余。
在松溪等地區,都構建了全面的區域性戰略布局,加強發展的系統性、市場前瞻性、產品創新能力,以專注、極致的服務理念,為客戶提供成都網站設計、成都網站建設 網站設計制作定制網站制作,公司網站建設,企業網站建設,高端網站設計,成都全網營銷推廣,外貿網站建設,松溪網站建設費用合理。
我們利用延遲調用來優化代碼。定義后的defer代碼,會在return之前返回,讓代碼顯得更加緊湊,且可讀性變強,對上面的代碼改造如下:
我們通過這個示例來看一下延遲調用與正常代碼之間的執行順序
先簡單分析一下代碼邏輯:
從輸出中,我們可以觀察到如下現象:
從這個實例中,我們很明顯觀察到,defer語句是在return之前執行
如果一個函數內定義了多個defer,則調用順序為LIFO(后進先出)方式執行。
仍然是相同的例子,但是在TestDefer中我們定義了三個defer輸出,根據LIFO原則,輸出的順序是3rd-2nd-1st,根據最后的結果,也是逆向向上執行defer輸出。
就在整理這篇筆記的時候,發現了自己的認知誤區,主要是本節實例三中發現的,先來看一下英文的描述:
對于上面的這段話的理解:
下面是代碼執行輸出,我們來一起分析一下:
雖然在a()函數內,顯示的返回了10,但是main函數中得到的結果是defer函數自增后的結果,我們來分析一下代碼:
在這篇文章的上一版,我曾經嘗試用指針取解釋defer修改返回值的類型,但是感覺不夠透徹,也讓閱讀者非常困惑,索性參考了一下go官方blog中的一篇文章,在此基礎上進行了擴展。如需要閱讀原文,可以參考下面的文章。
在開始之前,希望你計算一下 Part1 共占用的大小是多少呢?
輸出結果:
這么一算, Part1 這一個結構體的占用內存大小為 1+4+1+8+1 = 15 個字節。相信有的小伙伴是這么算的,看上去也沒什么毛病
真實情況是怎么樣的呢?我們實際調用看看,如下:
輸出結果:
最終輸出為占用 32 個字節。這與前面所預期的結果完全不一樣。這充分地說明了先前的計算方式是錯誤的。為什么呢?
在這里要提到 “內存對齊” 這一概念,才能夠用正確的姿勢去計算,接下來我們詳細的講講它是什么
有的小伙伴可能會認為內存讀取,就是一個簡單的字節數組擺放
上圖表示一個坑一個蘿卜的內存讀取方式。但實際上 CPU 并不會以一個一個字節去讀取和寫入內存。相反 CPU 讀取內存是 一塊一塊讀取 的,塊的大小可以為 2、4、6、8、16 字節等大小。塊大小我們稱其為 內存訪問粒度 。如下圖:
在樣例中,假設訪問粒度為 4。 CPU 是以每 4 個字節大小的訪問粒度去讀取和寫入內存的。這才是正確的姿勢
另外作為一個工程師,你也很有必要學習這塊知識點哦 :)
在上圖中,假設從 Index 1 開始讀取,將會出現很崩潰的問題。因為它的內存訪問邊界是不對齊的。因此 CPU 會做一些額外的處理工作。如下:
從上述流程可得出,不做 “內存對齊” 是一件有點 "麻煩" 的事。因為它會增加許多耗費時間的動作
而假設做了內存對齊,從 Index 0 開始讀取 4 個字節,只需要讀取一次,也不需要額外的運算。這顯然高效很多,是標準的 空間換時間 做法
在不同平臺上的編譯器都有自己默認的 “對齊系數”,可通過預編譯命令 #pragma pack(n) 進行變更,n 就是代指 “對齊系數”。一般來講,我們常用的平臺的系數如下:
另外要注意,不同硬件平臺占用的大小和對齊值都可能是不一樣的。因此本文的值不是唯一的,調試的時候需按本機的實際情況考慮
輸出結果:
在 Go 中可以調用 unsafe.Alignof 來返回相應類型的對齊系數。通過觀察輸出結果,可得知基本都是 2^n ,最大也不會超過 8。這是因為我手提(64 位)編譯器默認對齊系數是 8,因此最大值不會超過這個數
在上小節中,提到了結構體中的成員變量要做字節對齊。那么想當然身為最終結果的結構體,也是需要做字節對齊的
接下來我們一起分析一下,“它” 到底經歷了些什么,影響了 “預期” 結果
在每個成員變量進行對齊后,根據規則 2,整個結構體本身也要進行字節對齊,因為可發現它可能并不是 2^n ,不是偶數倍。顯然不符合對齊的規則
根據規則 2,可得出對齊值為 8。現在的偏移量為 25,不是 8 的整倍數。因此確定偏移量為 32。對結構體進行對齊
Part1 內存布局:axxx|bbbb|cxxx|xxxx|dddd|dddd|exxx|xxxx
通過本節的分析,可得知先前的 “推算” 為什么錯誤?
是因為實際內存管理并非 “一個蘿卜一個坑” 的思想。而是一塊一塊。通過空間換時間(效率)的思想來完成這塊讀取、寫入。另外也需要兼顧不同平臺的內存操作情況
在上一小節,可得知根據成員變量的類型不同,其結構體的內存會產生對齊等動作。那假設字段順序不同,會不會有什么變化呢?我們一起來試試吧 :-)
輸出結果:
通過結果可以驚喜的發現,只是 “簡單” 對成員變量的字段順序進行改變,就改變了結構體占用大小
接下來我們一起剖析一下 Part2 ,看看它的內部到底和上一位之間有什么區別,才導致了這樣的結果?
符合規則 2,不需要額外對齊
Part2 內存布局:ecax|bbbb|dddd|dddd
通過對比 Part1 和 Part2 的內存布局,你會發現兩者有很大的不同。如下:
仔細一看, Part1 存在許多 Padding。顯然它占據了不少空間,那么 Padding 是怎么出現的呢?
通過本文的介紹,可得知是由于不同類型導致需要進行字節對齊,以此保證內存的訪問邊界
那么也不難理解,為什么 調整結構體內成員變量的字段順序 就能達到縮小結構體占用大小的疑問了,是因為巧妙地減少了 Padding 的存在。讓它們更 “緊湊” 了。這一點對于加深 Go 的內存布局印象和大對象的優化非常有幫
基本設計思路:
類型轉換、類型斷言、動態派發。iface,eface。
反射對象具有的方法:
編譯優化:
內部實現:
實現 Context 接口有以下幾個類型(空實現就忽略了):
互斥鎖的控制邏輯:
設計思路:
(以上為寫被讀阻塞,下面是讀被寫阻塞)
總結,讀寫鎖的設計還是非常巧妙的:
設計思路:
WaitGroup 有三個暴露的函數:
部件:
設計思路:
結構:
Once 只暴露了一個方法:
實現:
三個關鍵點:
細節:
讓多協程任務的開始執行時間可控(按順序或歸一)。(Context 是控制結束時間)
設計思路: 通過一個鎖和內置的 notifyList 隊列實現,Wait() 會生成票據,并將等待協程信息加入鏈表中,等待控制協程中發送信號通知一個(Signal())或所有(Boardcast())等待者(內部實現是通過票據通知的)來控制協程解除阻塞。
暴露四個函數:
實現細節:
部件:
包: golang.org/x/sync/errgroup
作用:開啟 func() error 函數簽名的協程,在同 Group 下協程并發執行過程并收集首次 err 錯誤。通過 Context 的傳入,還可以控制在首次 err 出現時就終止組內各協程。
設計思路:
結構:
暴露的方法:
實現細節:
注意問題:
包: "golang.org/x/sync/semaphore"
作用:排隊借資源(如錢,有借有還)的一種場景。此包相當于對底層信號量的一種暴露。
設計思路:有一定數量的資源 Weight,每一個 waiter 攜帶一個 channel 和要借的數量 n。通過隊列排隊執行借貸。
結構:
暴露方法:
細節:
部件:
細節:
包: "golang.org/x/sync/singleflight"
作用:防擊穿。瞬時的相同請求只調用一次,response 被所有相同請求共享。
設計思路:按請求的 key 分組(一個 *call 是一個組,用 map 映射存儲組),每個組只進行一次訪問,組內每個協程會獲得對應結果的一個拷貝。
結構:
邏輯:
細節:
部件:
如有錯誤,請批評指正。
GO語言的優勢:可直接編譯成機器碼,不依賴其他庫,glibc的版本有一定要求,部署就是扔一個文件上去就完成了。靜態類型語言,但是有動態語言的感覺,靜態類型的語言就是可以在編譯的時候檢查出來隱藏的大多數問題,動態語言的感覺就是有很多的包可以使用,寫起來的效率很高。語言層面支持并發,這個就是Go最大的特色,天生的支持并發,我曾經說過一句話,天生的基因和整容是有區別的,大家一樣美麗,但是你喜歡整容的還是天生基因的美麗呢?Go就是基因里面支持的并發,可以充分的利用多核,很容易的使用并發。內置runtime,支持垃圾回收,這屬于動態語言的特性之一吧,雖然目前來說GC不算完美,但是足以應付我們所能遇到的大多數情況,特別是Go1.1之后的GC。簡單易學,Go語言的作者都有C的基因,那么Go自然而然就有了C的基因,那么Go關鍵字是25個,但是表達能力很強大,幾乎支持大多數你在其他語言見過的特性:繼承、重載、對象等。豐富的標準庫,Go目前已經內置了大量的庫,特別是網絡庫非常強大,我最愛的也是這部分。內置強大的工具,Go語言里面內置了很多工具鏈,最好的應該是gofmt工具,自動化格式化代碼,能夠讓團隊review變得如此的簡單,代碼格式一模一樣,想不一樣都很困難。跨平臺編譯,如果你寫的Go代碼不包含cgo,那么就可以做到window系統編譯linux的應用,如何做到的呢?Go引用了plan9的代碼,這就是不依賴系統的信息。Go語言這么多的優勢,你還不想學嗎?我記得當時我看的是黑馬程序員的視頻,我對他們視頻的印象就是通俗易懂,就是好!