Go Interview
- 代碼效率分析,考察局部性原理
- 多核CPU場景下,cache如何保持一致、不衝突? MESI協議 MESI協議是一個基於失效的緩存一致性協議,是支持寫回(write-back)緩存的最常用協議
- uint類型溢出
- 介紹rune類型
- 編程題:3個函數分別打印cat、dog、fish,要求每個函數都要起一個goroutine, 按照cat、dog、fish順序打印在屏幕上100次。 https://go.dev/play/p/OJ5N82ammVV
func main() {
catCh := make(chan struct{})
dogCh := make(chan struct{})
fishCh := make(chan struct{})
wg := sync.WaitGroup{}
wg.Add(3)
go printCat(&wg, catCh, dogCh)
go printDog(&wg, dogCh, fishCh)
go printFish(&wg, fishCh, catCh)
wg.Wait()
}
func printCat(wg *sync.WaitGroup, catCh chan struct{}, dogCh chan<- struct{}) {
defer wg.Done()
for i := 0; i < 100; i++ {
fmt.Println("cat")
dogCh <- struct{}{}
<-catCh
}
}
func printDog(wg *sync.WaitGroup, dogCh <-chan struct{}, fishCh chan<- struct{}) {
defer wg.Done()
for i := 0; i < 100; i++ {
<-dogCh
fmt.Println("dog")
fishCh <- struct{}{}
}
}
func printFish(wg *sync.WaitGroup, fishCh <-chan struct{}, catCh chan<- struct{}) {
defer wg.Done()
for i := 0; i < 100; i++ {
<-fishCh
fmt.Println("fish")
catCh <- struct{}{}
}
}
- 介紹一下channel, 無緩沖和有緩衝區別
- 無緩沖的通道在被創建時,必須指定其容量為 0,也就是無緩沖。這種通道的特點是在發送操作和接收操作時,必須同時進行,否則會發生阻塞
有緩沖通道在被創建時,必須指定其容量大於 0。這種通道的特點是在發送操作和接收操作時,可以異步進行,也就是說當 goroutine 向有緩沖通道發送數據時,只要通道還沒有被填滿,該 goroutine 就可以繼續執行,而不會被阻塞
是否了解channel底層實現,比如實現channel的數據結構是什麼? channel 的底層數據結構是一個帶緩存的環形陣列,其中包含一個指向陣列開始位置的指針和一個指向陣列結束位置的指針。在使用通道時,Golang 會自動維護這些指針,以實現數據的發送和接收。 可參考
https://github.com/gophercon/2017-talks/blob/master/KavyaJoshi-UnderstandingChannels/Kavya%20Joshi%20-%20Understanding%20Channels.pdf
具體來說,當一個 goroutine 向一個有緩衝的通道發送數據時,Golang 會將數據存儲在通道的緩存區中,並將通道的指針向後移動一個位置。當另一個 goroutine 從該通道接收數據時,Golang 會從緩存區中讀取數據,並將通道的指針向前移動一個位置。當通道的指針到達陣列的末尾時,它們會自動返回陣列的開始位置,這樣就可以實現通道的循環使用。
需要注意的是,channel 的底層數據結構是由 Golang 自動管理的,開發者不需要關心其實現細節。而且,Golang 提供的 channel 操作都是原子的,這意味著多個 goroutine 可以安全地訪問同一個通道,而不用擔心競爭條件和死鎖問題。
- channel是否線程安全? 是, gorountine safe , 詳細看上一題
- Mutex是悲觀鎖還是樂觀鎖?悲觀鎖、樂觀鎖是什麼? Go 中的 Mutex 是悲觀鎖。 悲觀鎖是一種經典的線程同步機制, 它假設多個線程之間的競爭很激烈,因此當一個線程需要訪問共享資源時, 它會將該資源加鎖,以防止其他線程訪問該資源。 當一個線程加鎖後,其他線程需要等待該線程釋放鎖之後才能訪問該資源。
相對的,樂觀鎖假設多個線程之間的競爭不會太激烈,因此當一個線程需要訪問共享資源時,它不會馬上加鎖,而是先試著訪問該資源,如果訪問成功就直接執行,否則才加鎖,並且重新嘗試訪問。這樣可以減少鎖的使用次數,提高程式的效率。
Mutex幾種模式?
普通模式: 在這種模式下,Mutex 可以被一個 goroutine 加鎖,其他 goroutine 需要等待該 goroutine 釋放鎖之後才能加鎖。 使用普通模式的 Mutex 可以保護單一共享資源的訪問,但是如果有多個 goroutine 需要訪問同一共享資源, 那麼普通模式下的 Mutex 就會導致所有 goroutine 都需要等待單一 goroutine 釋放鎖之後才能訪問該共享資源,效率會變得很低下。
讀寫模式: 在這種模式下,Mutex 可以被多個 goroutine 同時以讀取模式加鎖,但是只能被一個 goroutine 以寫入模式加鎖。 當 Mutex 被以讀取模式加鎖時,其他 goroutine 也可以以讀取模式加鎖, 但是當 Mutex 被以寫入模式加鎖時,其他所有 goroutine 都需要等待該 goroutine 釋放鎖之後才能加鎖。 使用讀寫模式的 Mutex 可以提高程式的效率,因為多個 goroutine 可以同時以讀取模式訪問共享資源,只有在有一個 goroutine 想要修改該共享資源時, 其他 goroutine 才需要等待。這種模式適合用在共享資源以讀取為主,寫入較少的情況下,比如緩存等。
需要注意的是,在讀寫模式下,如果有一個 goroutine 以寫入模式加鎖,那麼其他 goroutine 就不能以讀取或寫入模式加鎖,這會導致其他 goroutine 需要等待寫入的 goroutine 釋放鎖之後才能繼續訪問共享資源。
Mutex可以做自旋鎖(Spin Lock)嗎? Mutex 通常是一種在多執行緒程式設計中用來控制對共享資源訪問的機制。當一個執行緒試圖訪問一個被 Mutex 保護的共享資源時,如果該 Mutex 已經被另一個執行緒鎖定,則該執行緒會被阻塞,直到 Mutex 釋放為止。
相對而言,自旋鎖是一種在多執行緒環境中實現互斥的方法,它不會像 Mutex 一樣阻塞執行緒,而是讓執行緒在一個循環中自旋等待,直到鎖定資源的執行緒釋放鎖為止。
因此,Mutex 和自旋鎖有所不同,而且在實現上也有很多不同的方式。一般來說,Mutex 通常是基於操作系統提供的原子鎖或信號量實現的,而自旋鎖則通常是基於原子操作實現的。這就意味著,如果你使用 Mutex 去實現自旋鎖,那麼當 Mutex 被鎖定時,其他執行緒將被阻塞,而不是進行自旋等待。
因此,通常不建議將 Mutex 用作自旋鎖。如果你需要實現自旋鎖,最好使用專門的自旋鎖算法。常見的自旋鎖算法包括 TAS(Test-And-Set)、TTAS(Test-Test-And-Set)、Ticket 等。這些算法可以通過原子操作來實現互斥,而不會像 Mutex 一樣阻塞執行緒。
通過引入自旋,保證任何時候都有處於等待狀態的自旋 M,避免在等待可用的 P 和 G 時頻繁的阻塞和喚醒
自旋鎖是一種非阻塞鎖,也就是說,如果某線程需要獲取自旋鎖,但該鎖已經被其他線程佔用時,該線程不會被掛起,而是在不斷的消耗CPU的時間,不停的試圖獲取自旋鎖。
介紹一下 RWMutex
RWMutex 是一種讀寫鎖(ReadWrite Lock),也稱為共享鎖(Shared Lock)。它是一種特殊的互斥機制,允許多個執行緒同時讀取共享資源,但當有一個執行緒需要修改共享資源時,其他執行緒就必須等待。
RWMutex 通常有兩種狀態:讀狀態和寫狀態。當一個執行緒想要讀取共享資源時,它必須先取得 RWMutex 的讀鎖(Read Lock),這樣其他執行緒也可以繼續讀取共享資源,但不能寫入。當一個執行緒想要修改共享資源時,它必須先取得 RWMutex 的寫鎖(Write Lock),這樣其他執行緒就不能讀取或寫入共享資源了。
RWMutex 的好處在於它可以提高共享資源的並發性。當多個執行緒需要讀取共享資源時,它們可以同時持有讀鎖,從而實現並行讀取,這樣可以提高系統的效率。而當一個執行緒需要修改共享資源時,它只需等待其他執行緒釋放讀鎖或寫鎖,就可以獲得寫鎖,從而進行修改。這樣可以避免多個執行緒同時修改共享資源,從而減少了競爭和錯誤的發生。
在實現上,RWMutex 可以使用各種不同的算法實現。例如,它可以使用原子操作實現,也可以使用操作系統提供的信號量和條件變量實現。
- 項目中用過的鎖?
- 介紹一下線程安全的共享內存方式 在多執行緒的程式中,共享內存是一種常見的通訊方式,讓不同的執行緒可以存取相同的資源。然而, 如果不採取特殊的措施,共享內存的存取可能會引發競爭條件(Race Condition)和死鎖(Deadlock)等問題, 因此需要特殊的技術來保護共享內存的一致性和可用性。下面是一些常用的線程安全的共享內存方式:
Mutex:使用 Mutex(互斥鎖)可以實現對共享內存的獨佔存取,即同一時間只有一個執行緒可以存取共享內存。當一個執行緒需要存取共享內存時,它必須先取得 Mutex 的鎖,這樣其他執行緒就不能存取該內存,從而實現了線程安全。Mutex 可以使用標準庫提供的互斥鎖,也可以使用操作系統提供的信號量和條件變量等方式實現。
Read-Write Lock:Read-Write Lock(讀寫鎖)是一種特殊的共享內存控制方式,允許多個執行緒同時讀取共享內存,但只有一個執行緒可以寫入。當一個執行緒需要讀取共享內存時,它必須先取得 Read-Write Lock 的讀鎖,這樣其他執行緒也可以繼續讀取共享內存,但不能寫入。當一個執行緒需要修改共享內存時,它必須先取得 Read-Write Lock 的寫鎖,這樣其他執行緒就不能讀取或寫入共享內存了。Read-Write Lock 可以使用標準庫提供的讀寫鎖,也可以使用操作系統提供的信號量和條件變量等方式實現。
原子操作:原子操作是一種不可分割的操作,可以保證在任何情況下都不會被其他執行緒中斷。使用原子操作可以實現對共享內存的原子讀寫操作,即讀取或修改共享內存時,不會被其他執行緒中斷,從而保證了共享內存的一致性和可用性。原子操作可以使用標準庫提供的原子操作,也可以使用操作系統提供的原子指令或者內存屏障等方式實現。
- 介紹一下 goroutine concurrency/01_Introduction/02_Process_vs_Threads
- goroutine自旋佔用cpu如何解決(go調用,gmp) 在 Go 中,goroutine 的自旋通常是由於某些同步原語無法立即滿足等待條件而引起的。這種自旋通常會占用 CPU 資源,降低系統整體的效率。
為了解決這個問題,Go 引入了一個名為「休眠」(parking)的概念。當 goroutine 遇到自旋時,它可以將自己「休眠」,並將 CPU 資源交還給系統,以便其他 goroutine 使用。
具體而言,Go 使用一種名為「M:N」調度器的機制,將 M 個 goroutine 映射到 N 個 OS 線程上。 當某個 goroutine 遇到自旋時,調度器會將其設置為「休眠」狀態,並將其與所在的 OS 線程解除關聯。 此時,該 OS 線程可以繼續運行其他 goroutine,或者釋放 CPU 資源。 當等待條件得到滿足時,goroutine 會被重新調度,並分配到一個可用的 OS 線程上運行。 通過這種方式,Go 可以最大化地利用 CPU 資源,同時保持高效的 goroutine 調度性能。
- Scheduler Tracing In Go
- 介紹linux系統信號
- goroutine搶占時機 (gc 棧掃描)
- Gc觸發時機
- 監控線程 runtime.sysmon 定時調用;
- 手動調用 runtime.GC 函數進行垃圾收集;
- 申請內存時 runtime.mallocgc 會根據堆大小判斷是否調用; gc-觸發時機
- 是否了解其他gc機制
- Go內存管理方式
- Channel分配在棧(stack)上還是堆(heap).上?哪些對象分配在堆上,哪些對級分配在棧上?
- 介紹一下大對像小對象,為什麼小對像多了會道成gc壓力? 內存優化
在 Go 語言中,小對象指的是分配在堆上的小記憶體塊,這些對象的大小通常不超過 32 字節。 在 Go 中,當使用 new() 或 make() 分配記憶體時,會在堆上分配一個記憶體塊來存儲這個對象。 在 Go 中,垃圾回收(GC)是自動進行的,當堆中的對象不再被引用時,垃圾回收器會回收這些對象所佔用的記憶體空間。 然而,當存在大量小對象時,這些對象的數量很容易增加,這會增加垃圾回收的壓力。 這是因為,當垃圾回收器需要回收一些記憶體時,它會遍歷整個堆來查找未被引用的對象。 如果堆中有大量的小對象,垃圾回收器需要遍歷的對象數量就會非常大,這會增加垃圾回收器的負擔,導致垃圾回收時間變長,進而影響程式的效能。 因此,在 Go 中,如果需要大量創建小對象,建議使用對象池(Object Pool)技術,將這些對象預先分配好,減少垃圾回收的壓力,提高程式效能。
- project中遇到的oom情況?
- project中使用go遇到的坑?
- 工作遇到的難題,有挑戰的事情,如何解決?
- 如何指定指令執行順序?