Golang quiz

any variable

any 有 static type 跟 dynamic type

var x any // i is nil and has static type any var v *T // v has value nil, static type *T x = 42 // x has value 42 and dynamic type int x = v // x has value (*T)(nil) and dynamic type *T

猜猜以下的結果

var x any fmt.Println(x == nil) x = (*int)(nil) fmt.Println(x == nil) fmt.Println(x == (*int)(nil))

atomic int

使用 atomic package 運算,有 Int64 & Int32 沒有 Int,
其中注意到有 atomic.Value 看起來很方便,從 Example 看到可以更新 Config

wg := sync.WaitGroup{} count := 10000 wg.Add(count) ans := atomic.Int32{} for i := 0; i < count; i++ { go func() { ans.Add(1) wg.Done() }() } wg.Wait() fmt.Println(ans) // call of fmt.Println copies lock value: // sync/atomic.Int32 contains sync/atomic.noCopy

會有警告

需要使用 ans.Load() 讀值

slice grows policy

https://github.com/golang/go/blob/master/src/runtime/slice.go#L177

小於 256 兩倍
其他的用 (newcap + 3*threshold) >> 2 ,當 newcap 很大的時候,相當於 newcap >> 2 也就是 1/4 = 0.25
runtime: make slice growth formula a bit smoother

func nextslicecap(newLen, oldCap int) int { newcap := oldCap doublecap := newcap + newcap if newLen > doublecap { return newLen } const threshold = 256 if oldCap < threshold { return doublecap } for { // Transition from growing 2x for small slices // to growing 1.25x for large slices. This formula // gives a smooth-ish transition between the two. newcap += (newcap + 3*threshold) >> 2 // We need to check `newcap >= newLen` and whether `newcap` overflowed. // newLen is guaranteed to be larger than zero, hence // when newcap overflows then `uint(newcap) > uint(newLen)`. // This allows to check for both with the same comparison. if uint(newcap) >= uint(newLen) { break } }

concurrency in map

原生的 map 是不支援 concurrency 的

func main() { m := map[int]bool{} n := 1000000 for i := range n { // go 1.22 supports range number go func() { m[i] = true }() } }

會導致 fatal error
而 sync.Map 是 concurrent safe
sync.Map 在以下兩種情況做最佳化

  1. when the entry for a given key is only ever written once but read many times, as in caches that only grow
  2. when multiple goroutines read, write, and overwrite entries for disjoint sets of keys.

不過在 GopherCon 2024 有位講者發現在多讀少寫情境下,map + RWMutex 的效能比 sync.Map 好

GC

A Guide to the Go Garbage Collector

Scheduling In Go

Go 使用 GMP model schedule
https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part1.html

Race Detector

https://go.dev/doc/articles/race_detector

pprof

https://darjun.github.io/2021/06/09/youdontknowgo/pprof/

database & system design

Transaction & ACID

What is Transaction?

Transaction is a sequence of operations that are treated as a single logical unit of work.

  • Atomicity: The result of transaction is all or nothing. Meaning that if one fails, the entire transaction fails and is rolled back.
  • Consistency: The database remains in a consistent state before and after the transaction.
  • Isolation: Transactions are isolated from each other. Intermediate results are not visible to other transactions.
  • Durability: Once transaction is committed, the changes are permanent, even in case of a system failure.

Example: Banking system

  • debit from Account A
  • credit to Account B
    If something went wrong during the transaction, e.g. failed to credit to Account B. The result is that the money in Account A lost and Account B didn't receive.
package main import ( "fmt" "log" "strings" "sync" "time" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/clause" ) type Account struct { ID string `gorm:"primaryKey"` Balance int } func main() { dsn := "host=localhost user=postgres password=postgres dbname=bankdb port=5432 sslmode=disable" db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) if err != nil { log.Fatal("connect failed:", err) } // 建立帳戶表 db.AutoMigrate(&Account{}) // 初始化帳戶 db.Clauses(clause.OnConflict{DoNothing: true}).Create(&[]Account{ {ID: "A", Balance: 100}, {ID: "B", Balance: 100}, }) // 執行並發測試 fmt.Println("Starting concurrent transfers...") testConcurrentTransfers(db) } // 實作轉帳邏輯(含交易、鎖、死鎖處理) func TransferMoney(db *gorm.DB, fromID, toID string, amount int) error { // 確保鎖定順序一致,避免死鎖 if fromID > toID { fromID, toID = toID, fromID } return db.Transaction(func(tx *gorm.DB) error { var from, to Account // 加寫鎖(FOR UPDATE) if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&from, "id = ?", fromID).Error; err != nil { return err } if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&to, "id = ?", toID).Error; err != nil { return err } // 檢查餘額 if from.Balance < amount { return fmt.Errorf("insufficient balance") } // 執行轉帳 from.Balance -= amount to.Balance += amount if err := tx.Save(&from).Error; err != nil { return err } if err := tx.Save(&to).Error; err != nil { return err } return nil }) } // 檢查是否為死鎖錯誤 func isDeadlock(err error) bool { return err != nil && strings.Contains(err.Error(), "deadlock detected") } // 並發測試 func testConcurrentTransfers(db *gorm.DB) { var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func(id int) { defer wg.Done() // 重試機制 for retry := 0; retry < 3; retry++ { err := TransferMoney(db, "A", "B", 10) if err != nil { if isDeadlock(err) { fmt.Printf("Worker %d: deadlock detected, retrying...\n", id) time.Sleep(100 * time.Millisecond) continue } fmt.Printf("Worker %d: failed transfer: %v\n", id, err) return } fmt.Printf("Worker %d: transfer success\n", id) return } }(i) } wg.Wait() // 查看轉帳後結果 var accounts []Account db.Find(&accounts) for _, acc := range accounts { fmt.Printf("Account %s balance: %d\n", acc.ID, acc.Balance) } }

Cache Strategy

https://www.jyt0532.com/2018/09/23/cache-mechanism/

Read

跟 cache 讀,沒讀到從 DB 讀,接著更新 cache

  • Inline-cache: Read through
  • Look-Aside-cache: Read aside

Write

只寫 cache: Write back
只寫 DB: Write around
都寫:Write through/ allocate on Write

Ref