go垃圾回收機制
參考自:https://zhuanlan.zhihu.com/p/334999060
go 1.3 標記清除法
缺點
go 1.5 三色標記法
屏障機制
插入屏障
但是如果棧不添加,當全部三色標記掃描之后,棧上有可能依然存在白色對象被引用的情況(如上圖的對象9). 所以要對棧重新進行三色標記掃描, 但這次為了對象不丟失, 要對本次標記掃描啟動STW暫停. 直到棧空間的三色標記結束。
刪除屏障
這種方式的回收精度低,一個對象即使被刪除了最后一個指向它的指針也依舊可以活過這一輪,在下一輪GC中被清理掉。
缺點
go 1.8 混合寫屏障
Golang中的混合寫屏障滿足弱三色不變式
,結合了刪除寫屏障和插入寫屏障的優點,只需要在開始時并發掃描各個goroutine的棧,使其變黑并一直保持,這個過程不需要STW,而標記結束后,因為棧在掃描后始終是黑色的,也無需再進行re-scan操作了,減少了STW的時間。
runtime.finalizer(終結器)
runtime.SetFinalizer 是 Go 語言中的一個函數,用于設置對象的終結器(finalizer)。終結器是一個回調函數,當對象被垃圾回收時會被調用,主要用于執行一些清理操作,如釋放資源或關閉文件句柄。
package mainimport ("fmt""runtime"
)type MyStruct struct {Name string
}func main() {obj := &MyStruct{Name: "Example"}runtime.SetFinalizer(obj, func(ms *MyStruct) {fmt.Printf("Finalizing: %s\n", ms.Name)})// 使對象可回收obj = nil// 手動觸發垃圾回收(僅用于測試)runtime.GC()// 等待終結器執行// time.Sleep(time.Second) // 可以加上這行來確保輸出
}
二維的map按行和按列讀取哪個快?
參考自 小林coding
golang里比較熟的庫?
web:gin
數據庫:GORM sqlx go-redis
配置管理:viper
日志管理:zap
消息隊列:NSQ
微服務:go-zero
圖形處理:barcode
Context放map有什么不好的?
Context 是 golang 中十分重要的接口,用于定義 goroutine 中的上下文信息,context 常用于以下幾種情況:
- 數據傳遞: 在多個 goroutine 中傳遞數據
- 超時管理: 通過配置超時時間,可以方便地配置協程的終止時間
- 終止協程: 通過使用 cancel() 方法,協程可以很方便地終止,可以批量管理多個協程的終止
Context 中放 map:
- 不適合存儲大對象:context 主要用于傳遞請求范圍的數據(如截止日期、取消信號等),而不是用于存儲大對象或共享狀態。
- 可能導致數據競爭:如果多個 goroutine 訪問同一個 map,可能會導致數據競爭,除非使用適當的同步機制。
- 增加內存壓力:將大型 map 存儲在 context 中可能導致不必要的內存占用,影響性能。
- 語義混淆:context 的設計初衷是為了傳遞請求范圍的值,使用它來存儲 map 可能導致代碼可讀性降低,其他開發者可能不容易理解上下文的使用意圖。
golang基本題:創建5個協程順序打印
package mainimport ("fmt""sync"
)var N = 5func main() {var wg sync.WaitGroupvar mu sync.Mutexch := make(chan int)wg.Add(1)go func(){defer wg.Done()for i:=1;i<=N;i++{ch<-i}close(ch)}()for i:=1;i<=N;i++{wg.Add(1)go func(){defer wg.Done()mu.Lock()// 即使協程 A 先接收到數據 1,協程 B 可能更早完成 fmt.Println 操作,導致輸出順序錯亂。fmt.Println(<-ch)mu.Unlock()}()}wg.Wait()
}
如何用go來實現死鎖
package mainimport ("fmt""sync""time"
)func main() {var m1, m2 sync.Mutexgo func(){// 獲取第一個鎖m1.Lock()defer m1.Unlock()time.Sleep(time.Second)fmt.Println("嘗試獲取第二個鎖。。。")m2.Lock()defer m2.Unlock()fmt.Println("協程1完成")}()go func(){// 獲取第一個鎖m2.Lock()defer m2.Unlock()time.Sleep(time.Second)fmt.Println("嘗試獲取第一個鎖。。。")m1.Lock()defer m1.Unlock()fmt.Println("協程2完成")}()select{}
}
線程進程區別?
進程:我們編寫的代碼只是一個存儲在硬盤的靜態文件,通過編譯后就會生成二進制可執行文件,當我們運行這個可執行文件后,它會被裝載到內存中,接著 CPU 會執行程序中的每一條指令,那么這個運行中的程序,就被稱為「進程」(Process)。
線程:線程是進程當中的一條執行流程。同一個進程內多個線程之間可以共享代碼段、數據段、打開的文件等資源,但每個線程各自都有一套獨立的寄存器和棧,這樣可以確保線程的控制流是相對獨立的。
GMP原理調度與線程相關
參考自:https://zhuanlan.zhihu.com/p/713236782
在 Go 語言中,協程被稱為 goroutine,它非常輕量,體積只有幾 KB,支持自動擴容。Go 語言采用 goroutine 和 channel 提供了更容易使用的并發方法,具體為讓一組可復用的函數運行在一組線程上,即使有協程阻塞,該線程的協程也可以被 runtime 調度,轉移到其他可運行的線程上。
GM模型
看上去挺像那么回事,但它存在如下缺點:
每個 M 在創建、銷毀、調度 G 的時候需要獲取鎖,形成了激烈的鎖競爭。
如果 M 在執行的某個 G 涉及到創建新協程,M 創建了 G’,此時 M 為了繼續執行 G,需要把 G’交給 M’執行,違背了局部性原則。(因為 G’是和 G 相關的,最好放在 M 上執行,而不是其他 M’)。
CPU 在 M 之間的頻繁切換導致系統開銷增大。
映射關系
GM 模型顯然不是一個好的協程調度模型,但軟件行業沒有什么東西是加一層解決不了的,因此 P(協程調度器) 油然而生。從協程的圖來看,一個用戶態協程綁定了一個內核態線程,即一個協程綁定一個線程。那么是否可以利用 P,嘗試不同的綁定方案?
G(協程)M(線程)P(Process)調度
- 全局 goroutine 隊列,同上面介紹的 GM 模型,用于存放等待運行的 G。
- P 的本地隊列:同全局隊列類似,存放的也是等待運行的 G,數量不超過 256 個。新建 G’ 時,G’ 優先加入到 P 的本地隊列,滿足局部性。如果隊列滿了,會將本地隊列的一半 G 和新創建的 G 打亂順序,一起放入全局隊列。
- P 列表:所有的 P 都在程序啟動時創建,并保存在數組中,最多有 GOMAXPROCS (可配置)個。
- M:線程想運行任務就得獲取 P,從 P 的本地隊列獲取 G,P 隊列為空時,M 也會嘗試從全局隊列拿一批 G 放到 P 的本地隊列,或從其他 P 的本地隊列偷一半放到自己 P 的本地隊列。M 運行 G,G 執行之后,M 會從 P 獲取下一個 G,不斷重復下去。
P 和 M 的數量
- P 的數量由 環境變量 $GOMAXPROCS 或者是由 runtime 的方法 GOMAXPROCS() 決定,當 P 的數量 n 確定以后,運行時系統會根據這個數量創建 n 個 P。
- go 程序啟動時,會設置 M 的最大數量,默認 10000,但是內核很難支持這么多的線程數。
- 當沒有足夠的 M 來關聯 P 并運行其中的可運行的 G 時,比如所有的 M 此時都阻塞住了,而 P 中還有很多就緒任務,就會去尋找空閑的 M,而沒有空閑的,就會去創建新的 M。
- M 與 P 的數量沒有絕對關系,一個 M 阻塞,P 就會去創建或者切換另一個 M,所以,即使 P 的默認數量是 1,也有可能會創建很多個 M。
4.調度器的設計策略
(1)work stealing 機制
當本線程無可運行的 G 時,嘗試從其他線程綁定 P 的隊列中偷取 G,而不是銷毀線程。
(2)hand off 機制
當本線程因為 G 進行系統調用阻塞時,線程釋放綁定的 P,把 P 轉移給其他空閑的線程執行。
(3)利用并行
P 的數量由 GOMAXPROCS 設置,最多有 GOMAXPROCS 個線程分布在多個 CPU上 同時運行。GOMAXPROCS 也限制了并發的程度,比如 GOMAXPROCS = 核數/2,則最多利用了一半的 CPU 核進行并行。
(4)搶占
在 coroutine 中要等待一個協程主動讓出 CPU 才執行下一個協程。在Go中,一個 goroutine 最多占用 CPU 10ms,防止其他 goroutine 被餓死。
(5)全局G隊列
在新的調度器中依然有全局 G 隊列,當 P 的本地隊列為空時,優先從全局隊列獲取,如果全局隊列為空時則通過 work stealing 機制從其他P的本地隊列偷取 G。
生命周期
1千個goroutine進行阻塞系統調用會怎么樣?
Go運行時(runtime)采用M:N調度模型,將goroutine復用在少量操作系統線程(M)上。當goroutine執行阻塞系統調用(如文件IO、同步網絡請求)時,當前線程會被操作系統掛起。此時,Go運行時會創建新的線程(通過pthread或Windows線程)來繼續運行其他goroutine。
結果:1000個阻塞系統調用會創建約1000個線程(每個阻塞調用占用一個線程)。
上限限制:Go默認允許最多10,000個線程(通過runtime/debug.SetMaxThreads設置),因此1000個線程不會觸發崩潰,但需注意資源消耗。
10個linux的命令
https://blog.csdn.net/qq_24950043/article/details/126294756
cat less tail(tail -f)
scp 文件名 目標服務器賬號@目標服務器ip:目標路徑
pwd
ps
kill (-9)
ssh 賬號@服務器ip -p 端口
nohup
grep
參考:https://blog.csdn.net/u012581020/article/details/131421817
grep match_pattern file_name
grep "match_pattern" file_name
1核,10w個goroutine會崩嗎?