1.gc垃圾回收算法:標(biāo)記-清除法
基本原理:從根(包括全局指針以及goroutine棧上指針)出發(fā),標(biāo)記可達(dá)節(jié)點(diǎn)為灰色,然后是繼續(xù)標(biāo)記灰色節(jié)點(diǎn)可達(dá)的節(jié)點(diǎn)為灰色肠阱,同時(shí)自身由灰色變?yōu)楹谏扇危粩嘀貜?fù),剩下所有的白色節(jié)點(diǎn)為垃圾回收節(jié)點(diǎn)
2.減輕gc負(fù)擔(dān)的根本方法是:減少對(duì)象數(shù)量,特別是減少小對(duì)象的大量創(chuàng)建
3.goroutine調(diào)度原理:半搶占式(在函數(shù)入口處插入?yún)f(xié)作代碼)的協(xié)作調(diào)度,可以理解為公平協(xié)作調(diào)度和搶占式調(diào)度的結(jié)合體
GPM模型,一個(gè)M對(duì)應(yīng)一個(gè)P對(duì)應(yīng)一個(gè)多個(gè)G的隊(duì)列蛉签,外加一個(gè)G的全局隊(duì)列
G被調(diào)度的時(shí)機(jī)有以下幾種場(chǎng)景:
a. 簡(jiǎn)單搶占式調(diào)度:一旦某個(gè)G中出現(xiàn)死循環(huán)或永久循環(huán)的代碼邏輯,那么G將永久占用分配給它的P和M沥寥,位于同一個(gè)P中的其他G將得不到調(diào)度碍舍,出現(xiàn)“餓死”的情況,在Go 1.2中實(shí)現(xiàn)了搶占式調(diào)度來解決這個(gè)問題邑雅,原理則是在每個(gè)函數(shù)或方法的入口片橡,加上一段額外的代碼,讓runtime有機(jī)會(huì)檢查是否需要執(zhí)行搶占調(diào)度(是否需要調(diào)度的判斷標(biāo)準(zhǔn)是由獨(dú)立的sysmon線程來維護(hù)的)蒂阱。這種解決方案只能說局部解決了“餓死”問題锻全,對(duì)于沒有函數(shù)調(diào)用,純算法循環(huán)計(jì)算的G录煤,scheduler依然無法搶占鳄厌。
b. channel阻塞、鎖阻塞( atomic, mutex, 或者 channel)等阻塞場(chǎng)景:如果G被阻塞在某個(gè)channel操作或者G被阻塞到同步互斥操作妈踊,也就是 Lock()了嚎,Unlock() 等鎖的情況下,G會(huì)被放置到某個(gè)wait隊(duì)列中,原來的M不會(huì)阻塞歪泳,會(huì)嘗試運(yùn)行下一個(gè)runnable的G萝勤,當(dāng)被放在wait隊(duì)列中的G不再阻塞時(shí),可以將G重新放到隊(duì)列中等待調(diào)度執(zhí)行
c. 異步system call 例如network I/O情況下的調(diào)度:當(dāng)G進(jìn)行network I/O操作時(shí)呐伞,G會(huì)被放置到某個(gè)wait隊(duì)列中敌卓,而M會(huì)嘗試運(yùn)行下一個(gè)runnable的G,當(dāng)被放在wait隊(duì)列中的G不再阻塞時(shí)伶氢,可以將G重新放到隊(duì)列中等待調(diào)度執(zhí)行
c. 同步system call例如文件io操作阻塞情況下的調(diào)度:如果G被阻塞在某個(gè)system call操作上趟径,那么不光G會(huì)阻塞,執(zhí)行該G的M也會(huì)解綁P(實(shí)質(zhì)是被sysmon搶走了)癣防,與G一起進(jìn)入sleep狀態(tài)蜗巧。如果此時(shí)有idle的M,則P與其綁定繼續(xù)執(zhí)行其他G蕾盯;如果沒有idle M幕屹,但仍然有其他G要去執(zhí)行,那么就會(huì)創(chuàng)建一個(gè)新M级遭。當(dāng)阻塞在syscall上的G完成syscall調(diào)用后望拖,G會(huì)去嘗試獲取一個(gè)可用的P,如果沒有可用的P装畅,那么G會(huì)被標(biāo)記為runnable靠娱,之前的那個(gè)sleep的M將再次進(jìn)入sleep。Go語言完全是自己封裝的系統(tǒng)調(diào)用掠兄,所以在封裝系統(tǒng)調(diào)用的時(shí)候,可以做不少手腳锌雀,也就是進(jìn)入系統(tǒng)調(diào)用的時(shí)候執(zhí)行entersyscall蚂夕,退出后又執(zhí)行exitsyscall函數(shù)。 也只有封裝了entersyscall的系統(tǒng)調(diào)用才有可能觸發(fā)重新調(diào)度腋逆。
4.slice與數(shù)組:slice是引用類型婿牍,數(shù)組是值類型,slice可以動(dòng)態(tài)擴(kuò)容惩歉,減少動(dòng)態(tài)內(nèi)存拷貝可以預(yù)先分配
5.channel原理:golang參考CSP模型實(shí)現(xiàn)的類似管道的結(jié)構(gòu)等脂,本質(zhì)是一個(gè)循環(huán)隊(duì)列+鎖+兩個(gè)Goroutine等待隊(duì)列,channel是不同goroutine間通信的消息通道
channel引起的panic場(chǎng)景:
a. 關(guān)閉一個(gè)未初始化(nil) 的 channel 撑蚌;
b. 重復(fù)關(guān)閉同一個(gè) channel上遥;
c. 向一個(gè)已關(guān)閉的 channel 中發(fā)送消息;
從一個(gè)已關(guān)閉的 channel 中讀取消息永遠(yuǎn)不會(huì)阻塞争涌,并且會(huì)返回一個(gè)為 false 的 ok標(biāo)記粉楚,可以用它來判斷 channel 是否關(guān)閉。
單向 channel 一般是在聲明時(shí)會(huì)用到,防止傳遞進(jìn)函數(shù)的channel被隨意讀取獲取寫入模软,限制操作伟骨,比如
func foo(ch chan<- int) <-chan int {...}
chan<- int 表示一個(gè)只可寫入的 channel,<-chan int 表示一個(gè)只可讀取的 channel燃异。上面這個(gè)函數(shù)約定了 foo 內(nèi)只能從向 ch 中寫入數(shù)據(jù)携狭,返回只一個(gè)只能讀取的 channel,這樣在方法聲明時(shí)約定可以防止 channel 被濫用回俐,這種約定在編譯期間就確定下來了逛腿。
channel的實(shí)現(xiàn)原理:channel內(nèi)部主要組成:一個(gè)環(huán)形數(shù)組實(shí)現(xiàn)的隊(duì)列,用于存儲(chǔ)消息元素鲫剿;兩個(gè)鏈表實(shí)現(xiàn)的 goroutine 等待隊(duì)列鳄逾,用于存儲(chǔ)阻塞在 recv 和 send 操作上的 goroutine;一個(gè)互斥鎖灵莲,用于各個(gè)屬性變動(dòng)的同步雕凹。
6.golang內(nèi)存泄漏場(chǎng)景:
a. 永遠(yuǎn)處于阻塞狀態(tài)的goroutine,這將導(dǎo)致這些 goroutine 中使用的許多代碼塊永遠(yuǎn)無法進(jìn)行垃圾收集政冻,如下場(chǎng)景案例:
如果將以下函數(shù)作為 goroutine 的啟動(dòng)函數(shù)并將一個(gè) nil channel 參數(shù)傳遞給它, 則 goroutine 將永遠(yuǎn)阻塞. Go 運(yùn)行時(shí)認(rèn)為 goroutine 仍然存活, 所以為 s 分配的內(nèi)存塊將永遠(yuǎn)不會(huì)被收集.
func k(c <-chan bool) {
s := make([]int64, 1e6)
if <-c { // 如果 c 為 nil, 這里將永遠(yuǎn)阻塞
_ = s
// 使用 s, ...
}
}
b.終結(jié)器(Finalizers):為循環(huán)引用組內(nèi)的成員設(shè)置 finalizer 可能會(huì)阻止為這個(gè)循環(huán)引用組分配的所有內(nèi)存塊被收集枚抵。
在下列函數(shù)被調(diào)用并退出之后, 為 x和 y 分配的內(nèi)存塊不保證在未來會(huì)被垃圾收集器回收.
func memoryLeaking() {
type T struct {
v [1<<20]int
t *T
}
var finalizer = func(t *T) {
fmt.Println("finalizer called")
}
var x, y T
// SetFinalizer 會(huì)使 x 逃逸到堆上.
runtime.SetFinalizer(&x, finalizer)
// 以下語句將導(dǎo)致 x 和 y 變得無法收集.
x.t, y.t = &y, &x // y 也逃逸到了 堆上.
}
7.interface與nil
go 中的接口分為兩種
var Xxxx interface{}
type Xxxx interface {
Show()
}
底層結(jié)構(gòu):
type eface struct { //空接口
_type *_type //類型信息
data unsafe.Pointer //指向數(shù)據(jù)的指針(go語言中特殊的指針類型unsafe.Pointer類似于c語言中的void*)
}
type iface struct { //帶有方法的接口
tab *itab //存儲(chǔ)type信息還有結(jié)構(gòu)實(shí)現(xiàn)方法的集合
data unsafe.Pointer //指向數(shù)據(jù)的指針(go語言中特殊的指針類型unsafe.Pointer類似于c語言中的void*)
}
data指向了nil 并不代表interface 是nil
8.golang內(nèi)存分配原理
new與make最終調(diào)用的都是mallocgc函數(shù)來進(jìn)行分配內(nèi)存,在啟動(dòng)時(shí)向操作系統(tǒng)預(yù)分配大塊內(nèi)存明场,內(nèi)存分配以TCMalloc的結(jié)構(gòu)進(jìn)行設(shè)計(jì), 不同大小的對(duì)象以不同class的span進(jìn)行分配,減少內(nèi)存碎片, 每個(gè)P都有cache減少鎖沖突汽摹,同時(shí)還有zero分配采用同一個(gè)全局對(duì)象, tiny分配器加快小對(duì)象分配的優(yōu)化。
9.切片會(huì)導(dǎo)致整個(gè)底層數(shù)組被鎖定
切片會(huì)導(dǎo)致整個(gè)底層數(shù)組被鎖定苦锨,底層數(shù)組無法釋放內(nèi)存逼泣。如果底層數(shù)組較大會(huì)對(duì)內(nèi)存產(chǎn)生很大的壓力。
func main() {
headerMap := make(map[string][]byte)
for i := 0; i < 5; i++ {
name := "/path/to/file"
data, err := ioutil.ReadFile(name)
if err != nil {
log.Fatal(err)
}
headerMap[name] = data[:1]
}
// do some thing
}
解決的方法是將結(jié)果克隆一份舟舒,這樣可以釋放底層的數(shù)組:
func main() {
headerMap := make(map[string][]byte)
for i := 0; i < 5; i++ {
name := "/path/to/file"
data, err := ioutil.ReadFile(name)
if err != nil {
log.Fatal(err)
}
headerMap[name] = append([]byte{}, data[:1]...)
}
// do some thing
}