【讀書筆記】《Go語言高級編程》——柴樹杉,曹春暉

這本書並不適合初學者閱讀,適合對Go語言的應用有一些心得,並希望能夠深入理解底層實現原理或者是希望能夠在Web開發方面結合Go語言來實現進階學習的技術人員學習和參考。

第一章:語言基礎。1、Go的基因來自CSP理論(貝爾實驗室)、面向對象和包的特性、C語言;2、數組、字符串和切片三者是密切相關的數據結構。這3種數據類型,在底層原始數據有着相同的內存結構,在上層,因爲語法的限制而有着不同的行爲表現。字符串底層數據也是對應的字節數組,但是字符串的只讀屬性禁止了在程序中對底層字節數組的元素的修改。切片的行爲更爲靈活,切片的結構和字符串結構類似,但是解除了只讀限制。切片的底層數據雖然也是對應數據類型的數組,但是每個切片還有獨立的長度和容量信息,切片賦值和函數傳參時也是將切片頭信息部分按傳值方式處理。因爲切片頭含有底層數據的指針,所以它的賦值也不會導致底層數據的複製;3、函數主要有具名和匿名之分,包級函數一般都是具名函數,具名函數是匿名函數的一種特例。函數還可以直接或間接地調用自己,也就是支持遞歸調用。每個Goroutine剛啓動時只會分配很小的棧(4KB或8KB,具體依賴實現),根據需要動態調整棧的大小,棧最大可以達到GB級,在Go 1.4以前,Go的動態棧採用的是分段式的動態棧,通俗地說就是採用一個鏈表來實現動態棧,每個鏈表的節點內存位置不會發生變化。爲了解決熱點調用的CPU緩存命中率問題,Go 1.4之後改用連續的動態棧實現,也就是採用一個類似動態數組的結構來表示棧。不過連續動態棧也帶來了新的問題:當連續棧動態增長時,需要將之前的數據移動到新的內存空間,這會導致之前棧中全部變量的地址發生變化。Go的接口類型是對其他類型行爲的抽象和概括,通過這種抽象的方式我們可以讓對象更加靈活和更具有適應能力。Go語言中接口類型的獨特之處在於它是滿足隱式實現的鴨子類型。所謂鴨子類型說的是:只要走起路來像鴨子、叫起來也像鴨子,那麼就可以把它當作鴨子。4、Goroutine之間是共享內存的。系統級線程都會有一個固定大小的棧(一般默認可能是2MB)。原子操作:sync.Mutex鎖,sync/atomic包,可以實現sync.Once的功能。注意同一個Goroutine並不能保證順序一致性。5、併發模型:通過鎖實現、channel實現、sync.WaitGroup等待組可以實現併發版的“Hello World”。生產者/消費者模型。發佈/訂閱模型。通過godco/vfs包、帶緩存的channel可是實現控制併發數量。注意併發的安全退出:例如select中加入time.After()超時回調或者default語句,也可以通過select來阻止main()函數退出,其中關閉select監聽的某個channel也可以控制併發的退出。其次,可以利用context包控制併發的退出;6、捕獲異常不是最終的目的。如果異常不可預測,直接輸出異常信息是最好的處理方式。recover()函數必須寫在defer的匿名函數裏面,必須要和有異常的棧幀只隔一個棧幀,recover()函數才能正常捕獲異常。換言之,recover()函數捕獲的是祖父一級調用函數棧幀的異常(剛好可以跨越一層defer()函數)!

第六章:分佈式系統。1、分佈式id生成器:Twitter的snowflake算法;2、分佈式鎖:基於Redis的setnx,基於ZooKeeper,基於etcd。如果發展到了分佈式服務階段,但在業務規模不大、每秒查詢數很小的情況下,使用哪種鎖方案都差不多。業務發展到一定量級的話,就需要從多方面來考慮了。首先是你的鎖是否在任何惡劣的條件下都不允許數據丟失,如果不允許,那麼就不要使用Redis的setnx的簡單鎖。如果對鎖數據的可靠性要求極高,那就只能使用etcd或者ZooKeeper這種通過一致性協議保證數據可靠性的鎖方案。但可靠的背後往往都是較低的吞吐量和較高的延遲;3、延時任務系統:定時器分爲時間堆(小頂堆實現)和時間輪。任務分發方案有消息隊列(可能會導致延時增加)和處理消息回調(可能會阻塞後續的任務執行)兩種模式;4、負載均衡:Fisher-Yates算法洗牌算法:每次隨機挑選一個值,放在數組末尾。然後在n-1個元素的數組中再隨機挑選一個值,放在數組末尾,依此類推。

附錄部分重點:1、數組是值傳遞,不是地址;2、recover()必須在defer函數中運行;3、不同Goroutine之間不滿足順序一致性內存模型;4、切片會導致整個底層數組被鎖定,底層數組無法釋放內存。如果底層數組較大需要及時釋放;5、空指針和空接口不等價;6、Goroutine未退出會泄漏內存。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章