mangreen / Some-Note

Development Memo
1 stars 0 forks source link

golang #28

Open mangreen opened 2 years ago

mangreen commented 2 years ago

golang面試題整理

slice & channel 的底层原理和注意事项

  1. unbuffered vs buffered channel
  2. make 與 new 的差別
  3. rune vs byte
  4. type assertion
  5. 使用 slice of pointers 或 slice of structs

常規性Golang面試題解析

https://www.jishuwen.com/d/2Oti/zh-tw

Go 實現常用設計模式

https://learnku.com/users/42861/articles?page=2

Go 易錯面試題彙總 (code)

https://learnku.com/articles/35063

type assertion

event, err := New(0)
if err != nil {
    t.Error(err)
}
_, ok := event.(Like)
if !ok {
    t.Error("Assertion error")
}

sync

https://juejin.cn/post/6844904175944351752

  1. sync.Mutex
  2. sync.RWMutex
  3. sync.WaitGroup
  4. sync.Once
  5. sync.Map
  6. sync.Pool
  7. sync.Cond

匿名函數語閉包

https://openhome.cc/Gossip/Go/Closure.html

var foo = func() func() {
var i int     
return func() {  
i++         
fmt.Println(i)     
} 
}() 
foo() // 1
foo() // 2
foo() // 3

Go semaphore

https://zhuanlan.zhihu.com/p/389718532

mangreen commented 1 year ago

Call by Value & Call by Reference

array & slice 參數 注意事項

https://kitecloud-backend.coderbridge.io/2020/08/15/golang-slice-%E4%BD%9C%E7%82%BA%E5%8F%83%E6%95%B8%E5%82%B3%E9%81%9E%E6%99%82%E7%9A%84%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A0%85/ Golang 傳遞參數進 func ,當 pass by value 時,會在進入 func 時複製一份。


func main() {
ary := [1]int{}
    fmt.Println("main", ary, fmt.Sprintf("%p", &ary))
test(ary)
fmt.Println("main", ary, fmt.Sprintf("%p", &ary))

}

func test(a [1]int) { a[0] = 1 fmt.Println("test", a, fmt.Sprintf("%p", &a)) } / main [0] 0xc000018030 test [1] 0xc000018038 main [0] 0xc000018030 /

可以看到傳入 func 的 array 記憶體位址改變了

同樣的情況放到 slice 就稍微有點不同:
```golang
func main() {
    slice := []int{}

        fmt.Println("main", slice, fmt.Sprintf("%p", &slice))
    test(slice)
    fmt.Println("main", slice, fmt.Sprintf("%p", &slice))
}

func test(s []int) {
    s[0] = 1
    fmt.Println("test", s, fmt.Sprintf("%p", &s))
}
/*
main [0] 0xc00000c030
test [1] 0xc00000c048
main [1] 0xc00000c030
*/

一樣可以發現傳進去的 slice 記憶體位址改變了, 但最後把 slice 內容印出來顯示 slice[0] 有正確改變成 1, 這是因為 slice 的資料其實不是儲存在它自己本身上的。 所以看起來很像 pass by value 可以修改到同一個 slice, 實際上還是在操作副本。

雖然副本 slice 可以操作同一個 array, 但如果是使用 append() 這種會改變 slice len 的操作, 就只會改到副本 slice 而不是原先的 slice,

func main() {
    slice := []int{0}
    fmt.Println("main", slice, fmt.Sprintf("%p", &slice))
    test(slice)
    fmt.Println("main", slice, fmt.Sprintf("%p", &slice))
}

func test(s []int) {
    s = append(s, 1)
    fmt.Println("test", s, fmt.Sprintf("%p", &s))
}
/*
main [0] 0xc00000c030
test [0 1] 0xc00000c060
main [0] 0xc00000c030
*/

slice 還沒超過 cap 時,你操作 slice 等於影響原始資料 array; 當 slice 持續 append 導致 cap 超過 array 長度時, 會自動幫你生成一個 2倍大的 array 、複製資料過去、並把 slice 指向這個新的 slice。此時你操作 slice 影響到的不是原本的 array 而是 2倍大的 array 所以在使用 slice 時要注意是否超過 cap 導致生成新的 array, 常見的陷阱是資料小於 cap 時可以正常透過 slice 修改 array, 一旦超過 cap 就會發現怎麼修改都無法改變原始的 array。

為何建構函式要使用 pointer

A: Go 是一個 pass by value 的程式語言,也就是每當我們把值放入函式中時,Go 會把這個值完整的複製一份,並放到新的記憶體位址

https://pjchender.dev/golang/pointers/

func main() {
    q := new(hp)
    q.name = "kevin"
    fmt.Println(q.name) // kevin

    q.rename1("ben")
    fmt.Println(q.name) // kevin

        q.rename2("ben")
    fmt.Println(q.name) // ben
}

type hp struct {
    name string
}

func (h hp) rename1(n string) {
    h.name = n
}

func (h *hp) rename2(n string) {
    h.name = n
}
mangreen commented 1 year ago

new(), T{}, &T{} 差別

func main() {
    q1 := new(hp)
    fmt.Println(unsafe.Sizeof(q1)) // 8

    q2 := hp{}
    fmt.Println(unsafe.Sizeof(q2)) // 16
    name := ""
    fmt.Println(unsafe.Sizeof(name)) // 16

    q3 := &hp{}
    fmt.Println(unsafe.Sizeof(q3)) // 8
}

type hp struct {
    name string
}
mangreen commented 1 year ago

go channel 關閉的那些事兒

https://juejin.cn/post/7033671944587182087

什麼情況下關閉 channel 會造成 panic ?

  1. 未初始化時關閉
  2. 重複關閉
  3. 關閉後傳送
  4. 傳送時關閉

    
    // 1.未初始化時關閉
    func TestCloseNilChan(t *testing.T) {
    var errCh chan error
    close(errCh)
    
    // Output:
    // panic: close of nil channel
    }

// 2.重複關閉 func TestRepeatClosingChan(t *testing.T) { errCh := make(chan error) var wg sync.WaitGroup wg.Add(1)

go func() { defer wg.Done() close(errCh) close(errCh) }()

wg.Wait()

// Output: // panic: close of closed channel }

// 3.關閉後傳送 func TestSendOnClosingChan(t *testing.T) { errCh := make(chan error) var wg sync.WaitGroup wg.Add(1)

go func() { defer wg.Done() close(errCh) errCh <- errors.New("chan error") }()

wg.Wait()

// Output: // panic: send on closed channel }

// 4.傳送時關閉 func TestCloseOnSendingToChan(t *testing.T) { errCh := make(chan error) var wg sync.WaitGroup wg.Add(1)

go func() { defer wg.Done() defer close(errCh)

  go func() {
     errCh <- errors.New("chan error") // 由於 chan 沒有緩衝佇列,程式碼會一直在此處阻塞
  }()

  time.Sleep(time.Second) // 等待向 errCh 傳送資料

}()

wg.Wait()

// Output: // panic: send on closed channel }


## Q: go channel 關閉後,讀取該 channel 會?
### A: 永遠不會阻塞,且只會輸出對應類型的零值。
```golang
func TestReadFromClosedChan(t *testing.T) {
   var errCh = make(chan error)

   go func() {
      defer close(errCh)
      errCh <- errors.New("chan error")
   }()

   go func() {
      for i := 0; i < 3; i++ {
         fmt.Println(i, <-errCh)
      }
   }()

   time.Sleep(time.Second)

   // Output:
   // 0 chan error
   // 1 <nil>
   // 2 <nil>
}
mangreen commented 1 year ago

Golang 中 Channel 对阻塞 goroutine 的唤醒顺序分析

https://blog.csdn.net/shida_csdn/article/details/88344017

Q: 當 Channel 可用時,它是按照什麼順序喚醒等待的 goroutine 協程的呢?

A: 先阻塞的先被喚醒!

多個 goroutine 等待讀取 ch,雖然誰先呼叫讀取是不確定的, 但是,誰先呼叫了讀取並被阻塞,誰就能在通道資料可用時先被喚醒

mangreen commented 1 year ago

Golang 的 GC學習

Go 垃圾回收

https://juejin.im/post/6844903917650722829 https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/752833/#outline__2

STW(Stop-The-World)觸發的時間

一次GC有兩次觸發STW,一次是GC的開始階段,主要是開啟寫屏障和輔助GC等操作 另外就是表記完成之後,重新掃描部分根對象,停用寫屏障

Go 什麼時候會觸發 GC?

https://cloud.tencent.com/developer/article/1900650 https://anakinsun.com/post/golang-gc/

1. 系統觸發:執行階段自行根據內建的條件,檢查、發現到,則進行 GC 處理,維護整個應用程式的可用性。

  1. gcTriggerHeap:當所分配的堆大小達到閾值(由控製器計算的觸發堆的大小)時,將會觸發。
  2. gcTriggerTime:當距離上一個 GC 週期的時間超過一定時間時,將會觸發。-時間週期以 runtime.forcegcperiod 變數為準,默認 2 分鐘。
  3. gcTriggerCycle:要求啟動新一輪的GC, 已啟動則跳過, 手動觸發GC的runtime.GC()會使用這個條件
  4. gcTriggerAlways: 強制觸發GC

2. 手動觸發:開發者在業務程式碼中自行呼叫 runtime.GC 方法來觸發 GC 行為。

在手動觸發的場景下,Go 語言中僅有 runtime.GC 方法可以觸發,也就沒什麼額外的分類的。

三色標記的過程

  1. 所有對象最開始都是白色。
  2. 從 root 開始找到所有可達對象,標記為灰色,放入待處理佇列。
  3. 遍歷灰色對象佇列,將其引用對象標記為灰色放入待處理佇列,自身標記為黑色。
  4. 處理完灰色對象佇列,執行清掃工作,將所有還是白色未被引用的對象刪除
mangreen commented 1 year ago

defer

defer 是延遲執行,可以將其後面的程式碼延後至函式結束時執行。Defer 可以 stack,執行順序先進後出

func main() {  
    fmt.Println("defer begin")
    defer fmt.Println("Hello World")
    defer fmt.Println("Hey World")
    defer fmt.Println("Hi World") 
    fmt.Println("defer end") 
}  
/* print out: 
defer begin 
defer end 
Hi World
Hey World
Hello World
*/

recover

recover 可捕捉系統自動產生或手動設定產生的 panic 錯誤,回復系統以避免程序崩潰。recover 必須和 defer 配合使用!recover 必須和 defer 配合使用!recover 必須和 defer 配合使用!(很重要)

package main
import "fmt"

func main() {
    fmt.Println("Taiwan: Here Comes 2021")

    defer func() {
        if err := recover(); err != nil {
            fmt.Println(err)
            fmt.Println("Don't panic! We got vaccinated!")
        }
        fmt.Println("Life still moves on!")
    }()

    panic("Oh! No! COVID Pandemic!")
}
mangreen commented 1 year ago

Goroutine 與 Thread 比較

https://studygolang.com/articles/23191?utm_source=tuicool&utm_medium=referral

程式 (Program)、進程 (Process)、線程 (Thread)、協程 (coroutine/goroutine)

https://ithelp.ithome.com.tw/articles/10242047

GPM

https://my.oschina.net/aom/blog/4279175 理解 M: Machine、P: Processor、G:Goroutine 三者的關係,可以通過經典的地鼠推車搬磚的模型來說明其三者關係: 地鼠(Gopher)的工作任務是:工地上有若干磚頭,地鼠借助小車把磚頭運送到火種上去燒製。M 就可以看作圖中的地鼠,P 就是小車,G 就是小車裡裝的磚。

Scheduler原理以及查看Goroutine执行

https://cloud.tencent.com/developer/news/327423