為什么要寫這本書
因為Go語言在服務端的開發(fā)效率、服務性能有著不俗的表現(xiàn),近幾年,Go 的熱度越來越高。國內外很多大公司都在大規(guī)模地使用Go。Google就不用說了,它是Go語言誕生的地方,其他公司如Meta(Facebook)、uber、騰訊、字節(jié)跳動、知乎、脈脈等都在擁抱和轉向Go。用Go 語言開發(fā)的著名開源項目也非常多,如k8s、docker、etcd、consul,每個名字都是如雷貫耳。
隨著市場對Go語言人才需求的不斷增長,很多開發(fā)人員都從其他語言,如PHP、C 、Java等轉投Go語言的懷抱。因為Go語言自身的特點和優(yōu)勢,這些轉型的開發(fā)人員也能寫出性能不錯的代碼。但是,由于沒有深入系統(tǒng)地學習Go的底層原理,在某些場景下,因為不懂底層原理,無法快速定位問題、無法進行性能優(yōu)化。
有些人說,語言并不重要,架構、技術選型這些才是根本。筆者覺得這個說法不完全對,架構、技術選型固然重要,但語言其實是開發(fā)人員每天都要打交道的東西,會用是遠遠不夠的,只有用好、知其所以然才能更全面地發(fā)揮其威力。
筆者自己親身經歷的一個事故是關于Go 1.14之前調度器的一個坑:執(zhí)行無限循環(huán)且沒有函數調用的 goroutine無法被搶占,導致程序表現(xiàn)出死機。因為之前我對這個坑的原理已經非常熟悉了。所以在事故現(xiàn)場,時間就明確了原因。后續(xù)的工作就是排查問題代碼,非常輕松。有些讀者要問了,既然你知道了坑的原理,為何還會掉進去?我只能說Bug是必然存在的,只是發(fā)現(xiàn)的早晚而已。有Bug不可怕,怕的是發(fā)現(xiàn)Bug卻無法定位出來。
當越來越多的開發(fā)人員都轉向Go語言時,如何在眾多求職者中脫穎而出便成了面試官和求職者共同面臨的一個問題。早期從其他語言轉過來的開發(fā)人員,以為只要簡單學習Go語言,能寫出可運行的代碼就可以了。但現(xiàn)在競爭越來越激烈,懂原理和只會寫代碼的人馬上就能被區(qū)分出來,那些抱殘守缺,秉承會用就行的理念的求職者,除非你在其他方面有出色的能力,否則你在職場上的競爭力就會很低。
現(xiàn)在網上流傳了很多看代碼打印結果的題目,我想說的是,這是把人腦當成了編譯器嗎?面試不是背八股文,不是記語言點:不用記住Go語言里的運算符的優(yōu)先級,不需要看出這個變量是否逃逸到了堆上,也不用背Go GC經歷了哪些階段……你只需要研究清楚它的原理,面試官問你什么問題就都能應對。
近一兩年,筆者在中文世界論壇里發(fā)表了很多篇與Go源碼閱讀相關的文章,也是在寫作本書的過程中做的功課。我通過看源碼、做實驗、請教大牛,對Go的理解逐漸加深。再去看很多文章就會感覺非常簡單,為什么這些我都能掌握?因為我研究過,我知道原理是什么,所以也知道你想要說什么。
后,希望通過本書,能讓你的Go水平真正上升一個臺階。
天道酬勤,與君共勉!
讀者交流及本書勘誤
由于篇幅有限,本書不可能涵蓋Go的所有內容,但關鍵的內容都呈現(xiàn)出來了,讀者有擴展閱讀及資源獲取需求,可加入猿媛之家讀者服務QQ群(496588733)進行交流。
本書為讀者提供了780分鐘的Go核心知識點講解,讀者可登錄網站https://golang.design/go- questions/獲取,同時本書后續(xù)的勘誤也將在該網站提供。讀者也可以關注下方公眾號進行批評指正。
本書的讀者對象
無論你是面試官,還是求職者,這本書都能讓你有所收獲。另外,本書內容不僅僅是對面試有幫助,所有寫Go的程序員都能從本書中有所收獲。
致謝
在寫作本書的過程中,和另一位學者歐長坤有很多交流討論,歐長坤是在讀博士,他對Go的理解非常深,他同時也是Go Contributor,我們的交流和討論讓我對很多問題有了更深入的理解,非常感謝。
我從Go夜讀社區(qū)的分享中學到了很多東西。并且我本人也擔任講師,分享了三期Go相關的內容,很多觀眾都表示很有幫助。教是好的學,我本人的收獲是多的。感謝Go夜讀社區(qū)的發(fā)起者楊文和SIG小組成員。
另外,我和Go圈的很多博客作者也有很多交流,收獲良多,在此一并感謝。
這兩年,我在碼農桃花源發(fā)表了很多文章,得到了很多讀者的肯定,這也是我能不斷寫作的動力,感謝你們。
饒全成
前言
第1部分 語 言 基 礎
第1章 逃逸分析/2
1.1 逃逸分析是什么/2
1.2 逃逸分析有什么作用/3
1.3 逃逸分析是怎么完成的/3
1.4 如何確定是否發(fā)生逃逸/4
1.5 Go與C/C 中的堆和棧是同一個概念嗎/5
第2章 延遲語句/6
2.1 延遲語句是什么/6
2.2 延遲語句的執(zhí)行順序是什么/7
2.3 如何拆解延遲語句/9
2.4 如何確定延遲語句的參數/10
2.5 閉包是什么/11
2.6 延遲語句如何配合恢復語句/11
2.7 defer鏈如何被遍歷執(zhí)行/13
2.8 為什么無法從父goroutine恢復子goroutine的panic/18
第3章 數據容器/20
3.1 數組與切片/20
3.1.1 數組和切片有何異同/20
3.1.2 切片如何被截。20
3.1.3 切片的容量是怎樣增長的/23
3.1.4 切片作為函數參數會被改變嗎/27
3.1.5 內建函數make和new的區(qū)別是什么/28
3.2 散列表map/29
3.2.1 map 是什么/29
3.2.2 map 的底層實現(xiàn)原理是什么/30
3.2.3 map 中的 key 為什么是無序的/50
3.2.4 map 是線程安全的嗎/50
3.2.5 float類型可以作為map的key嗎/50
3.2.6 map 如何實現(xiàn)兩種 get 操作/52
3.2.7 如何比較兩個 map 是否相等/53
3.2.8 可以對 map 的元素取地址嗎/54
3.2.9 可以邊遍歷邊刪除嗎/54
第4章 通道/55
4.1 CSP是什么/55
4.2 通道有哪些應用/56
4.3 通道的底結構/57
4.3.1 數據結構/57
4.3.2 創(chuàng)建過程/58
4.3.3 接收過程/60
4.3.4 發(fā)送過程/67
4.3.5 收發(fā)數據的本質/72
4.4 通道的關閉過程發(fā)生了什么/74
4.5 從一個關閉的通道里仍然能讀出數據嗎/75
4.6 如何優(yōu)雅地關閉通道/76
4.7 關于通道的happens-before有哪些/79
4.8 通道在什么情況下會引起資源泄漏/81
4.9 通道操作的情況總結/81
第5章 接口/82
5.1 Go接口與C 接口有何異同/82
5.2 Go語言與鴨子類型的關系/82
5.3 iface和eface的區(qū)別是什么/84
5.4 值接收者和指針接收者的區(qū)別/86
5.4.1 方法/86
5.4.2 值接收者和指針接收者/87
5.4.3 兩者分別在何時使用/89
5.5 如何用interface實現(xiàn)多態(tài)/89
5.6 接口的動態(tài)類型和動態(tài)值是什么/91
5.7 接口轉換的原理是什么/93
5.8 類型轉換和斷言的區(qū)別是什么/96
5.9 如何讓編譯器自動檢測類型是否實現(xiàn)了接口/101
第2部分 語 言 類 庫
第6章 unsafe/104
6.1 如何利用unsafe包修改私有成員/104
6.2 如何利用unsafe獲取slice和map的長度/105
6.3 如何實現(xiàn)字符串和byte切片的零復制轉換/106
第7章 context/108
7.1 context是什么/108
7.2 context有什么作用/108
7.3 如何使用context/109
7.3.1 傳遞共享的數據/109
7.3.2 定時取消/111
7.3.3 防止 goroutine 泄漏/111
7.4 context底層原理是什么/112
7.4.1 接口/113
7.4.2 結構體/114
第8章 錯誤/124
8.1 接口error是什么/124
8.2 接口error有什么問題/125
8.3 如何理解關于error的三句諺語/126
8.3.1 視錯誤為值/126
8.3.2 檢查并優(yōu)雅地處理錯誤/128
8.3.3 只處理錯誤一次/130
8.4 錯誤處理的改進/131
第9章 計時器/133
9.1 Timer底層數據結構為什么用四叉堆而非二叉堆/133
9.2 Timer曾做過哪些重大的改進/134
9.3 定時器的使用場景有哪些/134
9.4 Timer/Ticker 的計時功能有多準確/134
9.5 定時器的實現(xiàn)還有其他哪些方式/137
第10章 反射/140
10.1 反射是什么/140
10.2 什么情況下需要使用反射/140
10.3 Go語言如何實現(xiàn)反射/140
10.3.1 types 和 interface/141
10.3.2 反射的基本函數/144
10.3.3 反射的三大定律/149
10.4 如何比較兩個對象是否完全相同/149
10.5 如何利用反射實現(xiàn)深度拷貝/151
第11章 同步模式/154
11.1 等待組 sync.WaitGroup 的原理是什么/154
11.2 緩存池 sync.Pool/157
11.2.1 如何使用sync.Pool/157
11.2.2 sync.Pool 是如何實現(xiàn)的/162
11.3 并發(fā)安全散列表 sync.Map/174
11.3.1 如何使用 sync.Map/175
11.3.2 sync.Map 底層如何實現(xiàn)/176
第3部分 高 級 特 性
第12章 調度機制/184
12.1 goroutine 和線程有什么區(qū)別/184
12.2 Go sheduler 是什么/184
12.3 goroutine 的調度時機有哪些/186
12.4 M:N模型是什么/187
12.5 工作竊取是什么/187
12.6 GPM底層數據結構是怎樣的/188
12.7 scheduler 的初始化過程是怎樣的/193
12.8 主 goroutine 如何被創(chuàng)建/207
12.9 g0棧和用戶棧如何被切換/212
12.10 Go schedule循環(huán)如何啟動/217
12.11 goroutine如何退出/221
12.12 schedule循環(huán)如何運轉/226
12.13 M如何找工作/227
12.14 系統(tǒng)監(jiān)控sysmon后臺監(jiān)控線程做了什么/237
12.14.1 搶占進行系統(tǒng)調用的P/240
12.14.2 搶占長時間運行的P/243
12.15 異步搶占的原理是什么/247
第13章 內存分配機制/252
13.1 管理內存的動機是什么,通常涉及哪些組件/252
13.1.1 內存管理的動機/252
13.1.2 內存管理運行時的組件/252
13.1.3 內存的使用狀態(tài)/253
13.2 Go語言中的堆和棧概念與傳統(tǒng)意義上的堆和棧有什么區(qū)別/255
13.3 對象分配器是如何實現(xiàn)的/255
13.3.1 分配的基本策略/256
13.3.2 對象分配器的基本組件和層級/256
13.3.3 對象分配的產生條件和入口/259
13.3.4 大對象分配/261
13.3.5 小對象分配/262
13.3.6 微對象分配/264
13.4 頁分配器是如何實現(xiàn)的/265
13.4.1 頁的分配/265
13.4.2 跨度的分配/266
13.4.3 非托管對象與定長分配器/267
13.5 與內存管理相關的運行時組件還有哪些/269
13.5.1 執(zhí)行棧管理/269
13.5.2 垃圾回收器和拾荒器/271
13.6 衡量內存消耗的指標有哪些/272
13.7 運行時內存管理的演變歷程/278
13.7.1 演變過程/278
13.7.2 存在的問題/279
第14章 垃圾回收機制/280
14.1 垃圾回收的認識/280
14.1.1 垃圾回收是什么,有什么作用/280
14.1.2 根對象到底是什么/280
14.1.3 常見的垃圾回收的實現(xiàn)方式有哪些,Go語言使用的是什么/281
14.1.4 三色標記法是什么/281
14.1.5 STW是什么意思/282
14.1.6 如何觀察 Go 語言的垃圾回收現(xiàn)象/283
14.1.7 有了垃圾回收,為什么還會發(fā)生內存泄漏/286
14.1.8 并發(fā)標記清除法的難點是什么/288
14.1.9 什么是寫屏障、混合寫屏障,如何實現(xiàn)/289
14.2 垃圾回收機制的實現(xiàn)細節(jié)/291
14.2.1 Go語言中進行垃圾回收的流程是什么/291
14.2.2 觸發(fā)垃圾回收的時機是什么/292
14.2.3 如果內存分配速度超過了標記清除的速度怎么辦/294
14.3 垃圾回收的優(yōu)化問題/295
14.3.1 垃圾回收關注的指標有哪些/295
14.3.2 Go 的垃圾回收過程如何調優(yōu)/295
14.3.3 Go的垃圾回收有哪些相關的API,其作用分別是什么/305
14.4 歷史及演進/305
14.4.1 Go 歷史各個版本在垃圾回收方面的改進/305
14.4.2 Go在演化過程中還存在哪些其他設計,為什么沒有被采用/307
14.4.3 Go語言中垃圾回收還存在哪些問題/307
結束語/310