64道Go機制高頻題整理(附答案背誦版)

如何解決Golang uint 類型溢出問題?

Golang的uint類型溢出問題通常會在大量運算中發生,特別是在涉及到大量循環或者大數運算時。當uint類型的值超過其最大值時,它會發生溢出,然後從該類型的最小值開始循環。爲了解決這個問題,我們可以採取以下幾種方法:

  1. 使用更大的數據類型:例如,如果你正在使用uint32,你可以嘗試升級到uint64。這將提供更大的值範圍,從而減少溢出的可能性。

  2. 添加溢出檢查:在每次運算之後,你可以檢查結果是否小於任一操作數(假設我們只在正數上進行操作)。如果是這樣,那麼就發生了溢出。

例如:

func addUint32(a, b uint32) (uint32, error) {
    res := a + b
    if res < a || res < b {
        return 0, errors.New("uint32 overflow")
    }
    return res, nil
}
  1. 使用 math/big:對於非常大的數值,你也可以考慮使用 math/big 包中的 Int 類型。這個類型可以處理任意大小的數值,但是運算速度會慢一些。

例如:

import "math/big"

func addBigNumbers(a, b *big.Int) *big.Int {
    res := new(big.Int)
    res.Add(a, b)
    return res
}

這些方法可以幫助你避免Golang中的uint類型溢出問題。然而,必須注意,這也會增加代碼的複雜性和運行時間,因此在編寫代碼時,你需要權衡這些因素。

請說一下Golang map 使用注意的點,以及是否併發安全?

Go語言的map是一種非常方便的數據結構,它提供了快速的查找、插入、刪除操作。然而,在使用Go語言map時,我們需要注意以下幾點:

  1. map是引用類型,如果兩個map同時指向一個底層,那麼一個map的變動會影響到另一個map

  2. map的零值(Zero Value)是nil,對nil map進行任何添加元素的操作都會觸發運行時錯誤(panic)。因此,使用前必須先創建map,使用make函數,例如:m := make(map[string]int)

  3. map的鍵可以是任何可以用==!=操作符比較的類型,如字符串,整數,浮點數,複數,布爾等。但是,slicemap,和function類型不可以作爲map的鍵,因爲這些類型不能使用==!=操作符進行比較。

  4. map在使用過程中不保證遍歷順序,即:map的遍歷結果順序可能會不一樣,所以在需要順序的場合,要自行處理數據並排序。

  5. map進行的所有操作,包括讀取,寫入,刪除,都是不安全的。也就是說,如果你在一個goroutine中修改map,同時在另一個goroutine中讀取map,可能會觸發“concurrent map read and map write”的錯誤。

關於併發安全,Go語言的map不是併發安全的。併發情況下,對map的讀和寫操作需要加鎖,否則可能會因爲併發操作引起的競態條件導致程序崩潰。爲了在併發環境下安全使用map,可以使用Go語言的sync包中的sync.RWMutex讀寫鎖,或者使用sync.Map

舉個例子,如果你有一個map用於緩存數據,在多個goroutine中都可能訪問和修改這個map,這時你需要使用鎖來保證併發安全,代碼可能如下:

var m = make(map[string]int)
var mutex = &sync.RWMutex{}

// 寫入數據到map
func write(key string, value int) {
    mutex.Lock()
    m[key] = value
    mutex.Unlock()
}

// 從map中讀取數據
func read(key string) (int, bool) {
    mutex.RLock()
    defer mutex.RUnlock()
    value, ok := m[key]
    return value, ok
}

在這個例子中,我們使用sync.RWMutex讀寫鎖來保護map,在讀取map時使用讀鎖,在寫入map時使用寫鎖,這樣就可以在併發環境下安全的使用map了。

Go 可以限制運行時操作系統線程的數量嗎?

是的,Go語言可以限制運行時操作系統線程的數量。Go語言的運行時系統使用了自己的調度器,該調度器使用了M:N模型,也就是說,M個goroutine可以在N個操作系統線程上進行調度。我們可以通過設置環境變量GOMAXPROCS或使用runtime包中的GOMAXPROCS函數來限制Go程序可以使用的操作系統線程數。默認情況下,GOMAXPROCS的值爲系統的CPU核數。

例如,如果我們想限制Go程序使用的操作系統線程數爲2,我們可以這樣做:

package main

import (
	"fmt"
	"runtime"
)

func main() {
	runtime.GOMAXPROCS(2) // 設置Go程序可以使用的最大操作系統線程數爲2

	// 現在我們的Go程序最多隻會使用2個操作系統線程。
}

注意,雖然GOMAXPROCS可以限制Go程序可以使用的操作系統線程數,但這並不意味着應該隨意設置這個值。在大多數情況下,讓Go運行時自動決定使用多少個操作系統線程可以獲得最好的性能。

在實際應用中,比如你的Go程序在一個CPU核數非常多而且都處於高負載的機器上運行,你可能會希望限制Go程序使用的操作系統線程數,以防止過度競爭CPU資源。

什麼是協程泄露?

協程泄露(Goroutine leakage)是指在Go程序中,啓動的協程(goroutine)沒有正確地停止和釋放,這會導致系統資源(如內存)的持續消耗,進而可能影響到程序的性能,甚至導致程序崩潰。

協程泄露的原因通常有兩種:

  1. 有些協程在完成它們的工作後沒有被正確地停止。
  2. 有些協程因爲阻塞(例如,等待永遠不會到來的通道信息)而無法退出。

以下是一個協程泄露的例子:

func leakyFunction() {
    ch := make(chan int)

    go func() {
        val := 0
        for {
            ch <- val
            val++
        }
    }()
}

在上面的代碼中,協程會無限地向通道ch發送數據,這就導致了協程泄露,因爲這個協程永遠不會退出。

解決協程泄露的常見方式有:

  • 使用帶超時的操作,比如select語句配合time.After
  • 使用context包來傳遞取消信號。
  • 使用sync.WaitGroup等待所有的協程完成。

例如,我們可以修復上面的協程泄露問題,如下:

import (
    "context"
)

func nonLeakyFunction(ctx context.Context) {
    ch := make(chan int)

    go func() {
        val := 0
        for {
            select {
            case <-ctx.Done():
                return
            case ch <- val:
                val++
            }
        }
    }()
}

這樣,當context被取消或者超時時,協程就會停止運行,從而避免了協程泄露。

Golang的map 是線程安全的嗎?

Go語言的map不是線程安全的。在併發情況下,對map的讀和寫操作需要加鎖,否則可能會因爲併發操作引起的競態條件導致程序崩潰。如果你需要在多個goroutine中訪問和修改同一個map,你需要使用鎖來保證線程安全。

Go語言提供了sync包中的sync.RWMutex讀寫鎖,或者使用sync.Map來實現併發安全的map

下面是一個使用sync.RWMutex的例子:

var m = make(map[string]int)
var mutex = &sync.RWMutex{}

// 寫入數據到map
func write(key string, value int) {
    mutex.Lock()
    m[key] = value
    mutex.Unlock()
}

// 從map中讀取數據
func read(key string) (int, bool) {
    mutex.RLock()
    defer mutex.RUnlock()
    value, ok := m[key]
    return value, ok
}

在這個例子中,我們使用sync.RWMutex讀寫鎖來保護map,在讀取map時使用讀鎖,在寫入map時使用寫鎖,這樣就可以在併發環境下安全的使用map了。

簡述一下Golong中無緩衝的 channel 和 有緩衝的 channel 的區別?

在Go語言中,channel是用於在goroutines之間傳遞數據的主要方式。根據其是否有緩衝區,channel可以被分類爲無緩衝的channel和有緩衝的channel。

無緩衝的channel(Unbuffered Channel)

無緩衝的channel是默認的channel類型。當一個數據被髮送到無緩衝的channel時,發送操作會阻塞,直到有另一個goroutine從這個channel中接收這個數據。同樣地,當試圖從一個無緩衝的channel接收數據時,如果沒有數據可供接收,接收操作也會阻塞,直到有另一個goroutine發送數據到這個channel。因此,無緩衝的channel提供了一種強同步的通信機制,發送和接收操作在完成數據交換時都會阻塞,確保了數據在不同的goroutines之間精確地同步。

有緩衝的channel(Buffered Channel)

有緩衝的channel具有一個固定大小的緩衝區。當數據被髮送到有緩衝的channel時,如果緩衝區未滿,發送操作就會立即返回,否則發送操作會阻塞,直到有另一個goroutine從channel中接收數據並空出空間。當從一個有緩衝的channel接收數據時,如果緩衝區中有數據,接收操作就會立即返回,否則接收操作會阻塞,直到有另一個goroutine發送數據到channel。因此,有緩衝的channel提供了一種弱同步的通信機制,發送和接收操作可能不會阻塞,使得goroutines可以繼續執行其他的操作。

下面是一個例子來說明無緩衝和有緩衝channel的區別:

package main

import (
	"fmt"
	"time"
)

func main() {
	// 無緩衝的channel
	unbuffered := make(chan string)
	go func() {
		unbuffered <- "Hello, World!"
		fmt.Println("Sent message to unbuffered channel!")
	}()
	time.Sleep(3 * time.Second) // 模擬一些處理延遲
	fmt.Println(<-unbuffered)
	
	// 有緩衝的channel
	buffered := make(chan string, 1)
	go func() {
		buffered <- "Hello, World!"
		fmt.Println("Sent message to buffered channel!")
	}()
	time.Sleep(3 * time.Second) // 模擬一些處理延遲
	fmt.Println(<-buffered)
}

在這個例子中,我們會看到,儘管在無緩衝的channel的情況下,發送操作會阻塞,直到接收操作完成;而在有緩衝的channel的情況下,由於緩衝區有足夠的空間,發送操作會立即完成,不會阻塞。

簡述一下 Golang的垃圾回收機制?

Go語言的垃圾回收(Garbage Collection, GC)機制主要是用來自動釋放不再被程序使用的內存,以防止內存泄露。Go的垃圾回收器是併發的,也就是說,它在主程序運行的同時進行垃圾回收,這使得Go語言能夠更有效地管理內存。

以下是Go的垃圾回收機制的簡述:

  1. 標記清除(Mark and Sweep): Go的垃圾回收器主要使用的是標記清除算法。這個算法包含兩個階段:標記階段和清除階段。在標記階段,垃圾回收器會從根對象(root object,即全局變量、棧上的變量等)開始,找出所有可達的對象,並進行標記。在清除階段,垃圾回收器會遍歷堆中的所有對象,清除那些沒有被標記的對象,也就是不可達的對象。

  2. 併發執行(Concurrent Execution): Go語言的垃圾回收器並不會在運行時停止所有的用戶級線程(也就是協程)。相反,它使用了一種稱爲三色標記清除(Tri-color Mark and Sweep)的算法,使得垃圾回收器可以在主程序運行的同時進行垃圾回收。這種方式可以減少程序的暫停時間,提高程序的運行效率。

  3. 寫屏障(Write Barrier): 在併發標記階段,由於用戶程序和垃圾回收器是同時運行的,用戶程序可能會修改堆中的數據。爲了在這種情況下保證垃圾回收的正確性,Go的垃圾回收器使用了寫屏障技術。寫屏障會在用戶程序嘗試寫入一個指針時觸發,更新垃圾回收器的標記信息。

  4. 垃圾回收調度(GC Pacing): Go的垃圾回收器會根據程序的運行情況調整垃圾回收的時間,以達到最佳的內存使用效率和CPU消耗。這種機制被稱爲垃圾回收調度或GC Pacing。

總的來說,Go的垃圾回收機制通過併發執行、寫屏障和垃圾回收調度,實現了高效且精確的內存管理。

Golang中的Map是如何遍歷的?

在Go語言中,遍歷map主要使用for循環配合range關鍵字。以下是一個遍歷map的例子:

m := map[string]int{
    "apple":  1,
    "banana": 2,
    "cherry": 3,
}

for key, value := range m {
    fmt.Printf("Key: %s, Value: %d\n", key, value)
}

在這個例子中,range關鍵字會遍歷map中的每一個鍵值對,每次循環,keyvalue變量都會被設置爲當前遍歷到的鍵值對。

需要注意的是,Go語言中的map並不保證遍歷的順序,每次遍歷的結果可能不同。如果需要按照特定的順序遍歷map,你可能需要先將map的鍵(或鍵值對)放入一個切片,然後對切片進行排序,然後再進行遍歷。

簡述一下Golang的Map實現原理?

Go語言的map是一個非常方便和常用的數據結構,用於存儲鍵值對的集合。它的實現基於一種稱爲"哈希表"(Hash Table)的數據結構。

哈希表(Hash Table)

哈希表是一種使用哈希函數來計算數據存儲位置的數據結構。當你插入一個鍵值對到map時,Go會先使用哈希函數計算鍵的哈希值,然後根據這個哈希值決定這個鍵值對應該存儲在哪個位置。當你試圖訪問一個鍵的值時,Go會同樣計算這個鍵的哈希值,然後快速找到這個鍵值對在哈希表中的位置。這就是爲什麼map在查找一個鍵的值時,可以非常快速 —— 它的時間複雜度爲O(1)。

擴容(Resizing)

當哈希表的數據量逐漸增加,爲了保持高效的查找速度,哈希表可能需要進行擴容。擴容就是創建一個新的、更大的哈希表,然後將舊哈希表的所有數據遷移到新哈希表。在Go的map中,當填充因子(已存儲的數據量與哈希表大小的比值)達到一定閾值(通常是0.75),就會觸發擴容操作。

併發安全

值得注意的是,Go的map並不是併發安全的。這意味着,如果你在多個goroutine中同時讀寫一個map,可能會出現數據競爭的情況。爲了在併發環境中安全地使用map,你需要使用sync包提供的鎖,如sync.Mutexsync.RWMutex,或者使用sync.Map這個併發安全的map類型。

以下是一個簡單的使用map的例子:

package main

import (
	"fmt"
)

func main() {
	// 創建一個map
	m := make(map[string]int)

	// 插入一個鍵值對
	m["hello"] = 1

	// 訪問一個鍵的值
	fmt.Println(m["hello"]) // 輸出: 1

	// 刪除一個鍵
	delete(m, "hello")

	// 訪問一個不存在的鍵,將得到該類型的零值
	fmt.Println(m["hello"]) // 輸出: 0
}

在這個例子中,我們創建了一個map,插入了一個鍵值對,然後訪問了這個鍵的值,最後刪除了這個鍵。

Go語言中context 結構原理?說一說context 使用場景和用途?

在Go語言中,context是一個非常重要的概念,它爲我們提供了在跨API邊界和進程之間傳遞請求作用域的deadline,取消信號,和其他請求相關的值的能力。

context包定義了Context類型,它在API邊界和進程之間提供了一種傳遞deadline,取消信號,和其他請求相關的值的方式。一個Context的生命週期通常與請求處理的生命週期相同,並且可以包含在多個API調用和goroutines之間共享的數據和取消信號。

context的主要方法有:

  • Deadline:返回當前Context何時會被取消。如果Context不會被取消,則返回ok爲false。
  • Done:返回一個通道,當Context被取消或超時時,該通道會被關閉。
  • Err:返回Context爲何被取消。
  • Value:返回與Context相關的值,這些值必須是線程安全的。

Go語言的context包提供了兩個函數用於創建Context對象:context.Background()context.TODO(),前者通常用在主函數、初始化以及測試代碼中,表示一個空的Context,後者通常用在不確定應該使用什麼Context,或者函數以後會更新以便接收一個Context參數。

此外,context包還提供了WithCancelWithDeadlineWithTimeoutWithValue函數,用於從現有的Context派生出新的Context

context的主要使用場景有:

  1. 超時控制:我們可以通過context.WithTimeout創建一個超時的Context,當超時時間到達,該Context就會自動取消。

  2. 請求傳遞:在微服務或者併發編程的環境中,我們可以通過context.WithValue將請求相關的數據綁定到Context中,在函數調用鏈路上下游之間傳遞。

  3. 請求取消:我們可以通過context.WithCancelcontext.WithTimeout創建一個可被取消的Context,並在需要取消時調用Contextcancel函數。

以下是一個例子展示瞭如何使用context來控制超時:

func main() {
    // 創建一個超時時間爲1秒的Context
    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
    defer cancel()  // 在函數返回時取消Context

    select {
    case <-time.After(2 * time.Second):
        fmt.Println("overslept")
    case <-ctx.Done():
        fmt.Println(ctx.Err())  // context deadline exceeded
    }
}

在這個例子中,我們設置了一個1秒的超時,當超時時間到達,ctx.Done()通道就會接收到一個信號,從而觸發超時處理。

闡述一下 Go 的 select 底層數據結構和一些特性?

Go語言的select關鍵字用於處理同時來自多個通道的數據。它的基本工作原理是“隨機選擇”滿足條件的分支進行執行。如果沒有分支滿足條件(即所有通道都無法讀/寫),select會阻塞,直到有分支滿足條件。如果select包含default分支,當其他分支都不滿足條件時,default分支會被執行。

Go的select底層使用了一種名爲scase的結構體,表示一個select的一個分支,包含了通道和對應的操作類型(發送或接收)。同時,它還會使用一個名爲hchan的結構體來表示通道的內部結構。

以下是select的一些重要特性:

  • 公平性:在Go語言中,select語句會隨機選擇一個可運行的case執行,這保證了每一個case都有公平的機會被執行,避免了飢餓問題。

  • 非阻塞:如果select中所有的case都無法運行,而且存在default分支,那麼select就不會阻塞,而是執行default分支。

  • 可用於時間操作select經常和time.Aftertime.Tick等函數一起使用,用於實現超時操作或定時操作。

  • 可用於退出操作select經常和context一起使用,當接收到context的取消信號時,可以安全地退出協程。

以下是一個select的使用示例:

func selectExample(c1, c2 chan int, quit chan bool) {
    for {
        select {
        case v := <-c1:
            fmt.Println("Received from c1:", v)
        case v := <-c2:
            fmt.Println("Received from c2:", v)
        case <-quit:
            fmt.Println("Quit signal received. Exiting.")
            return
        default:
            fmt.Println("No data received.")
        }
    }
}

在這個示例中,selectc1c2quit三個通道中選擇一個可用的通道進行操作,如果都不可用,就執行default分支。

詳細敘述Golang中的Goroutine調度策略 ?

Go語言的併發模型基於goroutines和channels。goroutine是Go語言運行時環境中的輕量級線程,其主要特點是創建和銷燬的代價非常小,可以方便地創建大量的goroutine來處理併發任務。然而,如何有效地調度這些goroutine,使它們能夠在有限的硬件資源上運行,就需要依賴於Go的調度器。

Go的調度器採用了M:N調度模型,其中M代表的是用戶級別的線程(也就是goroutine),而N代表的是內核級別的線程。Go調度器的主要任務就是在N個OS線程(也被稱爲M)上調度M個goroutine。這種模型允許在少量的OS線程上運行大量的goroutine。

Go的調度器使用了三種隊列來管理goroutine:

  1. 全局隊列(Global Queue):此隊列中包含了所有剛創建的goroutine。

  2. 本地隊列(Local Queue):每個P(Processor,處理器)都有一個本地隊列,P會優先從本地隊列中取出goroutine來執行。

  3. 網絡輪詢器(Netpoller):此隊列中包含了所有在等待網絡事件(如IO操作)的goroutine。當網絡事件就緒時,對應的goroutine會被放入全局隊列中,等待被P取出執行。

Go的調度器採用了工作竊取(Work Stealing)和手動搶佔(Preemption)的策略:

  • 工作竊取:當一個P的本地隊列中沒有goroutine時,它會嘗試從全局隊列或其他P的本地隊列中竊取goroutine來執行。

  • 手動搶佔:爲了防止一個goroutine長時間佔用P而導致其他goroutine餓死,Go的調度器會定期的進行搶佔操作。在Go 1.14之前,Go的調度器只在函數調用時纔會進行搶佔操作。從Go 1.14開始引入了異步搶佔,即允許在任何安全點進行搶佔。

這種調度模型和策略使Go語言可以有效的利用硬件資源,處理大量的併發任務,同時也爲複雜的併發編程提供了簡潔的語言級別的支持。

請說一說Golang的Http包的內存泄漏 ?

Go語言的net/http包是用於處理HTTP請求和響應的庫,但是如果不正確使用,可能會導致內存泄漏。以下是一些常見的可能引起內存泄漏的場景及其解決方法:

  1. 未關閉Response.Body:當你使用http.Get或者http.Post等方法發送請求時,你需要在完成讀取響應體後關閉它。否則,連接可能無法被複用,導致內存泄漏。
resp, err := http.Get("http://example.com")
if err != nil {
    // handle err
}
defer resp.Body.Close() // Make sure to close the body
  1. 長時間運行的Handler:如果你的HTTP Handler需要很長時間才能完成,例如因爲需要進行復雜的計算或者等待其他資源,這可能會導致大量的協程被阻塞,消耗大量的內存。

這種情況下,你可以考慮使用一些異步處理的方式,例如將任務放入隊列,或者使用context來設置超時。

  1. 未處理的長連接:如果你的服務器需要處理長連接(例如WebSocket或者HTTP/2),你需要確保在連接不再需要時正確地關閉它。否則,這些連接可能會持續消耗內存。

  2. 大量的中間件:如果你使用了大量的中間件,這可能會導致每個請求需要大量的內存。你應該儘量減少中間件的使用,或者使用一些內存更高效的中間件。

總的來說,正確地使用net/http包並且注意處理所有的資源,可以避免大部分的內存泄漏問題。

Golang字符串轉成byte數組,會發生內存拷貝嗎?

在Go語言中,將字符串轉換爲字節切片([]byte)時,會發生內存拷貝。這是因爲在Go中,字符串是不可變的,而字節切片是可變的。爲了防止通過修改字節切片而間接修改字符串,Go在將字符串轉換爲字節切片時,會創建一個新的字節數組來存儲數據。

以下是一個簡單的例子:

s := "hello"
b := []byte(s)

在這個例子中,b是一個新的字節數組,它的內容是字符串s的一個副本。修改b不會影響到s

這意味着,如果你有一個非常大的字符串,將它轉換爲字節切片可能會消耗大量的內存。在處理大字符串時,需要特別注意這一點。

對已經關閉的的chan進行讀寫,會怎麼樣?爲什麼?

在Go語言中,對已經關閉的channel進行讀寫,結果會有所不同。

對已經關閉的channel進行讀操作:

你可以安全地從一個已經關閉的channel中讀取數據。如果channel中還有未讀取的數據,讀操作將成功並返回數據以及一個用於表明數據是否有效的標記(如果channel已經關閉並且數據有效,該標記爲false)。當channel中沒有數據可讀時,讀操作將返回零值以及一個false標記。這種特性使得我們可以通過range語句來讀取channel中的所有數據,直到channel被關閉。例如:

ch := make(chan int)
go func() {
    ch <- 1
    ch <- 2
    ch <- 3
    close(ch)
}()

for i := range ch {
    fmt.Println(i)
}

對已經關閉的channel進行寫操作:

如果你試圖向一個已經關閉的channel寫入數據,Go語言將拋出一個panic。這是因爲,一旦channel被關閉,你不能再向其添加新的數據。如果你試圖這麼做,Go認爲這是一個嚴重的編程錯誤,因此,它會panic。例如:

ch := make(chan int)
close(ch)
ch <- 1 // 這將會panic

這是因爲在Go的併發模型中,關閉channel是爲了告訴接收者,發送者沒有更多的數據要發送了。因此,一旦channel被關閉,就意味着不能再向其發送新的數據。這種約定幫助程序員在處理併發邏輯時,能夠更好地理解數據流的生命週期。

Golang中除了加Mutex鎖以外還有哪些方式可以安全讀寫共享變量?

在Go語言中,除了使用互斥鎖(Mutex)以外,還有其他幾種方式可以用於安全地讀寫共享變量:

  1. Channel:Go語言的核心理念之一就是“不通過共享內存來通信,而是通過通信來共享內存”。我們可以通過創建一個channel,然後通過發送和接收消息的方式來讀寫共享變量。這種方式在處理併發問題時非常有用,因爲channel本身就提供了併發安全性。

    c := make(chan int)
    
    go func() { // goroutine 1
        x := <-c
        fmt.Println(x)
    }()
    
    go func() { // goroutine 2
        c <- 10
    }()
    
  2. 讀寫鎖(sync.RWMutex:如果你的程序中讀操作遠多於寫操作,那麼使用讀寫鎖可能會比互斥鎖更有效率。讀寫鎖允許多個協程同時讀取變量,但是在寫入時會阻止其他協程讀寫。

    var rwMutex sync.RWMutex
    var sharedVar int
    
    // Writer
    go func() {
        rwMutex.Lock()
        sharedVar = 10
        rwMutex.Unlock()
    }()
    
    // Reader
    go func() {
        rwMutex.RLock()
        fmt.Println(sharedVar)
        rwMutex.RUnlock()
    }()
    
  3. 原子操作(sync/atomic包):對於一些簡單的數值和布爾類型,我們可以使用原子操作來讀寫共享變量,這會比使用互斥鎖更有效率。

    var sharedVar int32
    
    // Writer
    go func() {
        atomic.StoreInt32(&sharedVar, 10)
    }()
    
    // Reader
    go func() {
        fmt.Println(atomic.LoadInt32(&sharedVar))
    }()
    
  4. sync.Once:如果你的共享變量只需要被初始化一次,那麼可以使用sync.Once來確保初始化的併發安全性。

    var once sync.Once
    var sharedVar *SomeType
    
    // Initializer
    go func() {
        once.Do(func() {
            sharedVar = &SomeType{}
        })
    }()
    

以上這些方式都可以用於安全地讀寫共享變量,你可以根據具體的使用場景和需求來選擇最合適的方式。

Golang的併發模型是什麼?實現併發的原理是?

Go語言的併發模型基於"CSP"(Communicating Sequential Processes)理論,主要通過goroutine和channel來實現。

Goroutine 是Go語言中的輕量級線程,由Go運行時管理。創建一個Goroutine的代價比創建一個系統級別的線程要小很多,這讓Go可以同時運行大量的Goroutine。Go語言的調度器可以在一個操作系統線程上調度多個Goroutine,也可以將Goroutine在多個操作系統線程間進行切換,這使得Go程序可以充分利用多核CPU。

Channel 是Go語言中用於Goroutine之間通信的一種機制。Channel提供了一種安全的方法來交換數據,確保數據在同一時間只被一個Goroutine訪問,從而避免了數據競態。

併發模型的實現原理主要體現在Goroutine的實現和調度上。Go運行時包含了自己的調度器,負責管理和調度Goroutine。Go的調度器使用一個稱爲M:N調度的技術,其中M代表操作系統線程,N代表Goroutine。調度器可以在M個操作系統線程上調度執行N個Goroutine,即使在只有一個線程的情況下,也可以通過合理的調度使得所有Goroutine都有機會運行。

在使用Go進行併發編程時,主要是通過控制Goroutine和Channel進行操作,實現併發編程的主要方法有:Goroutine池,Channel組合,select多路複用,以及使用sync和context包提供的同步原語等。

Go中對nil的Slice和空Slice的處理是⼀致的嗎?

在Go中,nil slice和空slice並不完全相同,儘管它們的行爲在許多情況下是相似的。

nil slice:

var s []int

在這種情況下,s是一個nil slice。它的長度和容量都爲0,且它沒有指向任何底層的數組。

空slice:

s := []int{}

或者

s := make([]int, 0)

上述兩種情況下,s都是一個空的slice,它的長度和容量都爲0,但它有一個非nil的零長度的底層數組。

在許多操作中,nil和空slice的行爲是一樣的,比如獲取長度、容量、追加元素等。但在與nil比較時,它們的行爲就有所不同了:

var s1 []int
s2 := []int{}

fmt.Println(s1 == nil) // 輸出: true
fmt.Println(s2 == nil) // 輸出: false

在上述代碼中,nil slice和空slice在與nil進行比較時,結果是不同的。

Golang的內存模型中爲什麼小對象多了會造成GC壓力?

Go語言的垃圾回收器(GC)主要負責回收不再使用的內存,釋放出空間供其他對象使用。Go的GC是基於標記清除算法的,並且是併發的,這意味着GC可以在程序運行的同時進行。

當你在Go程序中創建很多小對象時,這些對象可能會分散在內存的各個區域,這使得垃圾回收器需要花費更多的時間和資源來標記和清除這些對象。同時,如果這些小對象被頻繁地創建和銷燬,那麼垃圾回收器需要更頻繁地運行,這也會增加GC的壓力。

此外,小對象的頻繁分配和回收可能會導致內存碎片化,進一步增加了GC的複雜性和壓力。因爲GC需要遍歷所有的內存區域來找到並標記所有活動的對象,如果內存被大量的小對象碎片化,那麼這個遍歷的過程就會更費時和費力。

因此,在設計和編寫Go程序時,應儘量避免頻繁地創建和銷燬小對象,並儘可能地複用對象,以減少GC的壓力。當然,這並不是說你應該避免創建小對象,而是說你應該在設計和編寫程序時,考慮到內存管理和GC的影響。

由於內容太多,更多內容以鏈接形勢給大家,點擊進去就是答案了

20. 如何解決Go中數據競爭問題?

21. 請說一說Golang 的 GC的觸發條件?

22. 闡述一下Go語言的棧空間管理 ?

23. 解釋一下Go中的鎖有哪些 ?

24. Channel是同步的還是異步的 ?

25. 闡述一下Goroutine和線程的區別?

26. 闡述一下Go的defer原理 ?

27. 闡述一下Go的select的運行原理 ?

28. Go主協程如何等其餘協程完再操作?

29. Go的Slice如何擴容 ?

30. 闡述一下Go中CAS算法 ?

31. 闡述一下Go中的逃逸分析?

32. 闡述一下Go的對象在內存中分配原理 ?

33. 解釋一下Go棧的內存是怎麼分配的 ?

34. 解釋一下Go堆內存管理分配原理 ?

35. Go函數中發生內存泄露的原因?

36. Golang協程爲什麼比線程輕量?

37. 線程模型有哪些?爲什麼 Go Scheduler 需要實現 M:N 的方案?Go Scheduler 由哪些元素構成呢?

38. 互斥鎖正常模式和飢餓模式有什麼區別 ?

39. 請說一下Go 原子操作有哪些?

40. Go 原子操作和鎖的區別有哪些 ?

41. 解釋一下Go work stealing 機制?

42. 解釋一下 Go hand off 機制 ?

43. Go如何查看運行時調度信息 ?

44. Mutex 有哪幾種模式?

45. Go 是 GC 算法是怎麼實現的?

46. GC 中 stw 時機,各個階段是怎麼解決的?

47. 說一說Golang中defer和return執行的先後順序 ?

48. grpc報錯rpc error:code=DeadlineExceeded desc = context deadline exceeded ?

49. Golang 記錄日誌的實現方式有哪些?

50. 說一說Golang 如何打包到Docker運行 ?

51. 解釋一下Go recover的執行時機 ?

52. 閉包錯誤引用同一個變量問題如何處理 ?

53. Go在循環內部執行defer語句會發生什麼 ?

54. 說一說Go語言觸發異常的場景有哪些 ?

55. 如何解決Data Race問題?

56. Go 如何部署運行 ?

57. 怎麼在Golang中實現協程池?

58. 說一說Golang中的標準庫有哪些?

59. 解釋一下Golang中的大端序和小端序?

60. Golang中的通道和緩衝區是什麼?

61. Golang中的管道是什麼?如何使用?

62. 解釋一下Golang中的sync.Once?

63. 簡述一下內存逃逸?什麼情況下會發生內存逃逸 ?

64. 字符串轉成byte數組,會發生內存拷貝嗎 ?

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