日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

Go 箴言

  • 不要通過共享內存進行通信,通過通信共享內存
  • 并發不是并行
  • 管道用于協調;互斥量(鎖)用于同步
  • 接口越大,抽象就越弱
  • 利用好零值
  • 空接口 interface{} 沒有任何類型約束
  • Gofmt 的風格不是人們最喜歡的,但 gofmt 是每個人的最愛
  • 允許一點點重復比引入一點點依賴更好
  • 系統調用必須始終使用構建標記進行保護
  • 必須始終使用構建標記保護 Cgo
  • Cgo 不是 Go
  • 使用標準庫的 unsafe 包,不能保證能如期運行
  • 清晰比聰明更好
  • 反射永遠不清晰
  • 錯誤是值
  • 不要只檢查錯誤,還要優雅地處理它們
  • 設計架構,命名組件,(文檔)記錄細節
  • 文檔是供用戶使用的
  • 不要(在生產環境)使用 panic()

Go 之禪

  • 每個 package 實現單一的目的
  • 顯式處理錯誤
  • 盡早返回,而不是使用深嵌套
  • 讓調用者處理并發(帶來的問題)
  • 在啟動一個 goroutine 時,需要知道何時它會停止
  • 避免 package 級別的狀態
  • 簡單很重要
  • 編寫測試以鎖定 package API 的行為
  • 如果你覺得慢,先編寫 benchmark 來證明
  • 適度是一種美德
  • 可維護性

代碼

使用go fmt格式化

讓團隊一起使用官方的 Go 格式工具,不要重新發明輪子。
嘗試減少代碼復雜度。 這將幫助所有人使代碼易于閱讀。

多個 if 語句可以折疊成 switch

// NOT BAD
if foo() {
    // ...
} else if bar == baz {
    // ...
} else {
    // ...
}

// BETTER
switch {
case foo():
    // ...
case bar == baz:
    // ...
default:
    // ...
}

用chan struct{}來傳遞信號,chan bool表達的不夠清楚

當你在結構中看到 chan bool 的定義時,有時不容易理解如何使用該值,例如:

type Service struct {
    deleteCh chan bool // what does this bool mean? 
}

但是我們可以將其改為明確的 chan struct {} 來使其更清楚:我們不在乎值(它始終是 struct {}),我們關心可能發生的事件,例如:

type Service struct {
    deleteCh chan struct{} // ok, if event than delete something.
}

30 * time.Second比time.Duration(30) * time.Second更好

你不需要將無類型的常量包裝成類型,編譯器會找出來。
另外最好將常量移到第一位:

// BAD
delay := time.Second * 60 * 24 * 60

// VERY BAD
delay := 60 * time.Second * 60 * 24

// GOOD
delay := 24 * 60 * 60 * time.Second

用time.Duration代替int64+ 變量名

// BAD
var delayMillis int64 = 15000

// GOOD
var delay time.Duration = 15 * time.Second

按類型分組const聲明,按邏輯和/或類型分組var

// BAD
const (
    foo = 1
    bar = 2
    message = "warn message"
)

// MOSTLY BAD
const foo = 1
const bar = 2
const message = "warn message"

// GOOD
const (
    foo = 1
    bar = 2
)

const message = "warn message"

這個模式也適用于 var。

  • 每個阻塞或者 IO 函數操作應該是可取消的或者至少是可超時的
  • 為整型常量值實現 Stringer 接口https://godoc.org/golang.org/x/tools/cmd/stringer
  • 檢查 defer 中的錯誤
  defer func() {
      err := ocp.Close()
      if err != nil {
          rerr = err
      }
  }()
  • 不要在 checkErr 函數中使用 panic() 或 os.Exit()
  • 僅僅在很特殊情況下才使用 panic, 你必須要去處理 error
  • 不要給枚舉使用別名,因為這打破了類型安全https://play.golang.org/p/MGbeDwtXN3
  package main
  type Status = int
  type Format = int // remove `=` to have type safety

  const A Status = 1
  const B Format = 1

  func main() {
      println(A == B)
  }
  • 如果你想省略返回參數,你最好表示出來
    • _ = f() 比 f() 更好
  • 我們用 a := []T{} 來簡單初始化 slice
  • 用 range 循環來進行數組或 slice 的迭代
    • for _, c := range a[3:7] {...} 比 for i := 3; i < 7; i++ {...} 更好
  • 多行字符串用反引號(`)
  • 用 _ 來跳過不用的參數
  func f(a int, _ string) {}
  • 如果你要比較時間戳,請使用 time.Before 或 time.After ,不要使用 time.Sub 來獲得 duration (持續時間),然后檢查它的值。
  • 帶有上下文的函數第一個參數名為 ctx,形如:func foo(ctx Context, ...)
  • 幾個相同類型的參數定義可以用簡短的方式來進行
  func f(a int, b int, s string, p string)
  func f(a, b int, s, p string)
  • 一個 slice 的零值是 nilhttps://play.golang.org/p/pNT0d_Bunqvar s []int fmt.Println(s, len(s), cap(s)) if s == nil { fmt.Println("nil!") } // Output: // [] 0 0 // nil!https://play.golang.org/p/meTInNyxtk
  var a []string
  b := []string{}

  fmt.Println(reflect.DeepEqual(a, []string{}))
  fmt.Println(reflect.DeepEqual(b, []string{}))
  // Output:
  // false
  // true
  • 不要將枚舉類型與 <, >, <= 和 >= 進行比較使用確定的值,不要像下面這樣做:
  value := reflect.ValueOf(object)
  kind := value.Kind()
  if kind >= reflect.Chan && kind <= reflect.Slice {
    // ...
  }
  • 用 %+v 來打印數據的比較全的信息
  • 注意空結構 struct{}, 看 issue: https://github.com/golang/go/issues/23440more: https://play.golang.org/p/9C0puRUstrP
  func f1() {
    var a, b struct{}
    print(&a, "n", &b, "n") // Prints same address
    fmt.Println(&a == &b)     // Comparison returns false
  }

  func f2() {
    var a, b struct{}
    fmt.Printf("%pn%pn", &a, &b) // Again, same address
    fmt.Println(&a == &b)          // ...but the comparison returns true
  }
  • 包裝錯誤: http://github.com/pkg/errors
    • 例如: errors.Wrap(err, "additional message to a given error")
  • 在 Go 里面要小心使用 range:
    • for i := range a and for i, v := range &a ,都不是 a 的副本
    • 但是 for i, v := range a 里面的就是 a 的副本
    • 更多: https://play.golang.org/p/4b181zkB1O
  • 從 map 讀取一個不存在的 key 將不會 panic
    • value := map["no_key"] 將得到一個 0 值
    • value, ok := map["no_key"] 更好
  • 不要使用原始參數進行文件操作
    • 而不是一個八進制參數 os.MkdirAll(root, 0700)
    • 使用此類型的預定義常量 os.FileMode
  • 不要忘記為 iota 指定一種類型
    • https://play.golang.org/p/mZZdMaI92cI
    const (
      _ = iota
      testvar         // testvar 將是 int 類型
    )

vs

    type myType int
    const (
      _ myType = iota
      testvar         // testvar 將是 myType 類型
    )

不要在你不擁有的結構上使用encoding/gob

在某些時候,結構可能會改變,而你可能會錯過這一點。因此,這可能會導致很難找到 bug。

不要依賴于計算順序,特別是在 return 語句中。

  // BAD
  return res, json.Unmarshal(b, &res)

  // GOOD
  err := json.Unmarshal(b, &res)
  return res, err

防止結構體字段用純值方式初始化,添加_ struct {}字段:

type Point struct {
  X, Y float64
  _    struct{} // to prevent unkeyed literals
}

對于 Point {X:1,Y:1} 都可以,但是對于 Point {1,1} 則會出現編譯錯誤:

./file.go:1:11: too few values in Point literal

當在你所有的結構體中添加了 _ struct{} 后,使用 go vet 命令進行檢查,(原來聲明的方式)就會提示沒有足夠的參數。

為了防止結構比較,添加func類型的空字段

  type Point struct {
    _ [0]func() // unexported, zero-width non-comparable field
    X, Y float64
  }

http.HandlerFunc比http.Handler更好

用 http.HandlerFunc 你僅需要一個 func,http.Handler 需要一個類型。

移動defer到頂部

這可以提高代碼可讀性并明確函數結束時調用了什么。

JAVAScript 解析整數為浮點數并且你的 int64 可能溢出

用 json:"id,string" 代替

type Request struct {
  ID int64 `json:"id,string"`
}

并發

  • 以線程安全的方式創建單例(只創建一次)的最好選擇是 sync.Once不要用 flags, mutexes, channels or atomics
  • 永遠不要使用 select{}, 省略通道, 等待信號
  • 不要關閉一個發送(寫入)管道,應該由創建者關閉往一個關閉的 channel 寫數據會引起 panic
  • math/rand 中的 func NewSource(seed int64) Source 不是并發安全的,默認的 lockedSource 是并發安全的, see issue: https://github.com/golang/go/issues/3611更多: https://golang.org/pkg/math/rand/
  • 當你需要一個自定義類型的 atomic 值時,可以使用 atomic.Value

性能

  • 不要省略 defer在大多數情況下 200ns 加速可以忽略不計
  • 總是關閉 http body defer r.Body.Close()除非你需要泄露 goroutine
  • 過濾但不分配新內存
  b := a[:0]
  for _, x := range a {
      if f(x) {
        b = Append(b, x)
      }
  }

為了幫助編譯器刪除綁定檢查,請參見此模式_ = b [7]

  • time.Time 有指針字段 time.Location 并且這對 go GC 不好只有使用了大量的 time.Time 才(對性能)有意義,否則用 timestamp 代替
  • regexp.MustCompile 比 regexp.Compile 更好在大多數情況下,你的正則表達式是不可變的,所以你最好在 func init 中初始化它
  • 請勿在你的熱點代碼中過度使用 fmt.Sprintf. 由于維護接口的緩沖池和動態調度,它是很昂貴的。如果你正在使用 fmt.Sprintf("%s%s", var1, var2), 考慮使用簡單的字符串連接。如果你正在使用 fmt.Sprintf("%x", var), 考慮使用 hex.EncodeToString or strconv.FormatInt(var, 16)
  • 如果你不需要用它,可以考慮丟棄它,例如io.Copy(ioutil.Discard, resp.Body)HTTP 客戶端的傳輸不會重用連接,直到body被讀完和關閉。
  res, _ := client.Do(req)
  io.Copy(ioutil.Discard, res.Body)
  defer res.Body.Close()
  • 不要在循環中使用 defer,否則會導致內存泄露因為這些 defer 會不斷地填滿你的棧(內存)
  • 不要忘記停止 ticker, 除非你需要泄露 channel
  ticker := time.NewTicker(1 * time.Second)
  defer ticker.Stop()
  • 用自定義的 marshaler 去加速 marshaler 過程但是在使用它之前要進行定制!例如:https://play.golang.org/p/SEm9Hvsi0r
  func (entry Entry) MarshalJSON() ([]byte, error) {
    buffer := bytes.NewBufferString("{")
    first := true
    for key, value := range entry {
        jsonValue, err := json.Marshal(value)
        if err != nil {
            return nil, err
        }
        if !first {
            buffer.WriteString(",")
        }
        first = false
        buffer.WriteString(key + ":" + string(jsonValue))
    }
    buffer.WriteString("}")
    return buffer.Bytes(), nil
  }
  • sync.Map 不是萬能的,沒有很強的理由就不要使用它。
    • 了解更多: https://github.com/golang/go/blob/master/src/sync/map.go#L12
  • 在 sync.Pool 中分配內存存儲非指針數據
    • 了解更多: https://github.com/dominikh/go-tools/blob/master/cmd/staticcheck/docs/checks/SA6002
  • 為了隱藏逃生分析的指針,你可以小心使用這個函數::
    • 來源: https://go-review.googlesource.com/c/go/+/86976
  // noescape hides a pointer from escape analysis.  noescape is
  // the identity function but escape analysis doesn't think the
  // output depends on the input. noescape is inlined and currently
  // compiles down to zero instructions.
  //go:nosplit
  func noescape(p unsafe.Pointer) unsafe.Pointer {
    x := uintptr(p)
    return unsafe.Pointer(x ^ 0)
  }
  • 對于最快的原子交換,你可以使用這個 m := (*map[int]int)(atomic.LoadPointer(&ptr))
  • 如果執行許多順序讀取或寫入操作,請使用緩沖 I/O
    • 減少系統調用次數
  • 有 2 種方法清空一個 map:
    • 重用 map 內存 (但是也要注意 m 的回收)
  for k := range m {
    delete(m, k)
  }
  • 分配新的
  m = make(map[int]int)

模塊

  • 如果你想在 CI 中測試 go.mod (和 go.sum)是否是最新 https://blog.urth.org/2019/08/13/testing-go-mod-tidiness-in-ci/

構建

  • 用這個命令 go build -ldflags="-s -w" ... 去掉你的二進制文件
  • 拆分構建不同版本的簡單方法用 // +build integration 并且運行他們 go test -v --tags integration .
  • 最小的 Go Docker 鏡像https://twitter.com/bbrodriges/status/873414658178396160CGO_ENABLED=0 go build -ldflags="-s -w" app.go && tar C app | docker import - myimage:latest
  • run go format on CI and compare diff這將確保一切都是生成的和承諾的
  • 用最新的 Go 運行 Travis-CI,用 travis 1了解更多:https://github.com/travis-ci/travis-build/blob/master/public/version-aliases/go.json
  • 檢查代碼格式是否有錯誤 diff -u <(echo -n) <(gofmt -d .)

測試

  • 測試名稱 package_test 比 package 要好
  • go test -short 允許減少要運行的測試數
  func TestSomething(t *testing.T) {
    if testing.Short() {
      t.Skip("skipping test in short mode.")
    }
  }
  • 根據系統架構跳過測試
  if runtime.GOARM == "arm" {
    t.Skip("this doesn't work under ARM")
  }
  • 用 testing.AllocsPerRun 跟蹤你的內存分配https://godoc.org/testing#AllocsPerRun
  • 多次運行你的基準測試可以避免噪音。go test -test.bench=. -count=20

工具

  • 快速替換 gofmt -w -l -r "panic(err) -> log.Error(err)" .
  • go list 允許找到所有直接和傳遞的依賴關系
    • go list -f '{{ .Imports }}' package
    • go list -f '{{ .Deps }}' package
  • 對于快速基準比較,我們有一個 benchstat 工具。
    • https://godoc.org/golang.org/x/perf/cmd/benchstat
  • go-critic linter 從這個文件中強制執行幾條建議
  • go mod why -m <module> 告訴我們為什么特定的模塊在 go.mod 文件中。
  • GOGC=off go build ... 應該會加快構建速度 source
  • 內存分析器每 512KB 記錄一次分配。你能通過 GODEBUG 環境變量增加比例,來查看你的文件的更多詳細信息。
    • 來源:https://twitter.com/bboreham/status/1105036740253937664
  • go mod why -m <module> 告訴我們為什么特定的模塊是在 go.mod 文件中。

其他

  • dump goroutines https://stackoverflow.com/a/27398062/433041
  go func() {
    sigs := make(chan os.Signal, 1)
    signal.Notify(sigs, syscall.SIGQUIT)
    buf := make([]byte, 1<<20)
    for {
      <-sigs
      stacklen := runtime.Stack(buf, true)
      log.Printf("=== received SIGQUIT ===n*** goroutine dump...n%sn*** endn"  , buf[:stacklen])
    }
  }()
  • 在編譯期檢查接口的實現 var _ io.Reader = (*MyFastReader)(nil)
  • len(nil) = 0https://golang.org/pkg/builtin/#len
  • 匿名結構很酷
  var hits struct {
    sync.Mutex
    n int
  }
  hits.Lock()
  hits.n++
  hits.Unlock()
  • httputil.DumpRequest 是非常有用的東西,不要自己創建
    • https://godoc.org.NET/http/httputil#DumpRequest
  • 獲得調用堆棧,我們可以使用 runtime.Caller
    • https://golang.org/pkg/runtime/#Caller
  • 要 marshal 任意的 JSON, 你可以 marshal 為 map[string]interface{}{}
  • 配置你的 CDPATH 以便你能在任何目錄執行 cd github.com/golang/go
    • 添加這一行代碼到 bashrc(或者其他類似的) export CDPATH=$CDPATH:$GOPATH/src
  • 從一個 slice 生成簡單的隨機元素
    • []string{"one", "two", "three"}[rand.Intn(3)]

垃圾回收 垃圾回收、三色標記原理

垃圾回收就是對程序中不再使用的內存資源進行自動回收的操作。

常見的垃圾回收算法:

  • 引用計數:每個對象維護一個引用計數,當被引用對象被創建或被賦值給其他對象時引用計數自動加 +1;如果這個對象被銷毀,則計數 -1 ,當計數為 0 時,回收該對象。優點:對象可以很快被回收,不會出現內存耗盡或到達閥值才回收。缺點:不能很好的處理循環引用
  • 標記-清除:從根變量開始遍歷所有引用的對象,引用的對象標記“被引用”,沒有被標記的則進行回收。優點:解決了引用計數的缺點。缺點:需要 STW(stop the world),暫時停止程序運行。
  • 分代收集:按照對象生命周期長短劃分不同的代空間,生命周期長的放入老年代,短的放入新生代,不同代有不同的回收算法和回收頻率。優點:回收性能好缺點:算法復雜

三色標記法

  • 初始狀態下所有對象都是白色的。
  • 從根節點開始遍歷所有對象,把遍歷到的對象變成灰色對象
  • 遍歷灰色對象,將灰色對象引用的對象也變成灰色對象,然后將遍歷過的灰色對象變成黑色對象。
  • 循環步驟3,直到灰色對象全部變黑色。
  • 通過寫屏障(write-barrier)檢測對象有變化,重復以上操作
  • 收集所有白色對象(垃圾)。

STW(Stop The World)

  • 為了避免在 GC 的過程中,對象之間的引用關系發生新的變更,使得GC的結果發生錯誤(如GC過程中新增了一個引用,但是由于未掃描到該引用導致將被引用的對象清除了),停止所有正在運行的協程。
  • STW對性能有一些影響,Golang目前已經可以做到1ms以下的STW。

寫屏障(Write Barrier)

  • 為了避免GC的過程中新修改的引用關系到GC的結果發生錯誤,我們需要進行STW。但是STW會影響程序的性能,所以我們要通過寫屏障技術盡可能地縮短STW的時間。

造成引用對象丟失的條件:

一個黑色的節點A新增了指向白色節點C的引用,并且白色節點C沒有除了A之外的其他灰色節點的引用,或者存在但是在GC過程中被刪除了。以上兩個條件需要同時滿足:滿足條件1時說明節點A已掃描完畢,A指向C的引用無法再被掃描到;滿足條件2時說明白色節點C無其他灰色節點的引用了,即掃描結束后會被忽略 。

寫屏障破壞兩個條件其一即可

  • 破壞條件1:Dijistra寫屏障

滿足強三色不變性:黑色節點不允許引用白色節點 當黑色節點新增了白色節點的引用時,將對應的白色節點改為灰色

  • 破壞條件2:Yuasa寫屏障

滿足弱三色不變性:黑色節點允許引用白色節點,但是該白色節點有其他灰色節點間接的引用(確保不會被遺漏) 當白色節點被刪除了一個引用時,悲觀地認為它一定會被一個黑色節點新增引用,所以將它置為灰色

GPM 調度 和 CSP 模型

協程的深入剖析

CSP 模型?

CSP 模型是“以通信的方式來共享內存”,不同于傳統的多線程通過共享內存來通信。用于描述兩個獨立的并發實體通過共享的通訊 channel (管道)進行通信的并發模型。

GPM 分別是什么、分別有多少數量?

  • G(Goroutine):即Go協程,每個go關鍵字都會創建一個協程。
  • M(machine):工作線程,在Go中稱為Machine,數量對應真實的CPU數(真正干活的對象)。
  • P(Processor):處理器(Go中定義的一個摡念,非CPU),包含運行Go代碼的必要資源,用來調度 G 和 M 之間的關聯關系,其數量可通過 GOMAXPROCS() 來設置,默認為核心數。

M必須擁有P才可以執行G中的代碼,P含有一個包含多個G的隊列,P可以調度G交由M執行。

Goroutine調度策略

  • 隊列輪轉:P 會周期性的將G調度到M中執行,執行一段時間后,保存上下文,將G放到隊列尾部,然后從隊列中再取出一個G進行調度。除此之外,P還會周期性的查看全局隊列是否有G等待調度到M中執行。
  • 系統調用:當G0即將進入系統調用時,M0將釋放P,進而某個空閑的M1獲取P,繼續執行P隊列中剩下的G。M1的來源有可能是M的緩存池,也可能是新建的。
  • 當G0系統調用結束后,如果有空閑的P,則獲取一個P,繼續執行G0。如果沒有,則將G0放入全局隊列,等待被其他的P調度。然后M0將進入緩存池睡眠。
GO編程:小技巧

 

CHAN 原理 chan實現原理

結構體

   type hchan struct {
 qcount   uint  // 隊列中的總元素個數
 dataqsiz uint  // 環形隊列大小,即可存放元素的個數
 buf      unsafe.Pointer // 環形隊列指針
 elemsize uint16  //每個元素的大小
 closed   uint32  //標識關閉狀態
 elemtype *_type // 元素類型
 sendx    uint   // 發送索引,元素寫入時存放到隊列中的位置

 recvx    uint   // 接收索引,元素從隊列的該位置讀出
 recvq    waitq  // 等待讀消息的goroutine隊列
 sendq    waitq  // 等待寫消息的goroutine隊列
 lock mutex  //互斥鎖,chan不允許并發讀寫
}

讀寫流程

向 channel 寫數據:

若等待接收隊列 recvq 不為空,則緩沖區中無數據或無緩沖區,將直接從 recvq 取出 G ,并把數據寫入,最后把該 G 喚醒,結束發送過程。

若緩沖區中有空余位置,則將數據寫入緩沖區,結束發送過程。

若緩沖區中沒有空余位置,則將發送數據寫入 G,將當前 G 加入 sendq ,進入睡眠,等待被讀 goroutine 喚醒。

從 channel 讀數據

若等待發送隊列 sendq 不為空,且沒有緩沖區,直接從 sendq 中取出 G ,把 G 中數據讀出,最后把 G 喚醒,結束讀取過程。

如果等待發送隊列 sendq 不為空,說明緩沖區已滿,從緩沖區中首部讀出數據,把 G 中數據寫入緩沖區尾部,把 G 喚醒,結束讀取過程。

如果緩沖區中有數據,則從緩沖區取出數據,結束讀取過程。

將當前 goroutine 加入 recvq ,進入睡眠,等待被寫 goroutine 喚醒。

關閉 channel

1.關閉 channel 時會將 recvq 中的 G 全部喚醒,本該寫入 G 的數據位置為 nil。將 sendq 中的 G 全部喚醒,但是這些 G 會 panic。

panic 出現的場景還有:

  • 關閉值為 nil 的 channel
  • 關閉已經關閉的 channel
  • 向已經關閉的 channel 中寫數據

無緩沖 Chan 的發送和接收是否同步?

// 無緩沖的channel由于沒有緩沖發送和接收需要同步
ch := make(chan int)   
//有緩沖channel不要求發送和接收操作同步
ch := make(chan int, 2)  

channel 無緩沖時,發送阻塞直到數據被接收,接收阻塞直到讀到數據;channel有緩沖時,當緩沖滿時發送阻塞,當緩沖空時接收阻塞。

context 結構原理

用途

Context(上下文)是Golang應用開發常用的并發控制技術 ,它可以控制一組呈樹狀結構的goroutine,每個goroutine擁有相同的上下文。Context 是并發安全的,主要是用于控制多個協程之間的協作、取消操作。

GO編程:小技巧

 

數據結構

Context 只定義了接口,凡是實現該接口的類都可稱為是一種 context。

并發控制神器之Context

  type Context interface {
   Deadline() (deadline time.Time, ok bool)
   Done() <-chan struct{}
   Err() error
   Value(key interface{}) interface{}
}
  • 「Deadline」 方法:可以獲取設置的截止時間,返回值 deadline 是截止時間,到了這個時間,Context 會自動發起取消請求,返回值 ok 表示是否設置了截止時間。
  • 「Done」 方法:返回一個只讀的 channel ,類型為 struct{}。如果這個 chan 可以讀取,說明已經發出了取消信號,可以做清理操作,然后退出協程,釋放資源。
  • 「Err」 方法:返回Context 被取消的原因。
  • 「Value」 方法:獲取 Context 上綁定的值,是一個鍵值對,通過 key 來獲取對應的值。

競態、內存逃逸

并發控制,同步原語 sync 包

競態

資源競爭,就是在程序中,同一塊內存同時被多個 goroutine 訪問。我們使用 go build、go run、go test 命令時,添加 -race 標識可以檢查代碼中是否存在資源競爭。

解決這個問題,我們可以給資源進行加鎖,讓其在同一時刻只能被一個協程來操作。

  • sync.Mutex
  • sync.RWMutex

逃逸分析

面試官問我go逃逸場景有哪些,我???

「逃逸分析」就是程序運行時內存的分配位置(棧或堆),是由編譯器來確定的。堆適合不可預知大小的內存分配。但是為此付出的代價是分配速度較慢,而且會形成內存碎片。

逃逸場景:

  • 指針逃逸
  • 棧空間不足逃逸
  • 動態類型逃逸
  • 閉包引用對象逃逸

快問快答

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

Go 中 Goroutine 可以通過 Channel 進行安全讀寫共享變量。

golang中new和make的區別?

用new還是make?到底該如何選擇?

  • make 僅用來分配及初始化類型為 slice、map、chan 的數據。
  • new 可分配任意類型的數據,根據傳入的類型申請一塊內存,返回指向這塊內存的指針,即類型 *Type。
  • make 返回引用,即 Type,new 分配的空間被清零, make 分配空間后,會進行初始。

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

首先Go的JSON 標準庫對 nil slice 和 空 slice 的處理是不一致。

  • slice := make([]int,0):slice不為nil,但是slice沒有值,slice的底層的空間是空的。
  • slice := []int{} :slice的值是nil,可用于需要返回slice的函數,當函數出現異常的時候,保證函數依然會有nil的返回值。

協程和線程和進程的區別?

并發掌握,goroutine和channel聲明與使用!

  • 進程: 進程是具有一定獨立功能的程序,進程是系統資源分配和調度的最小單位。每個進程都有自己的獨立內存空間,不同進程通過進程間通信來通信。由于進程比較重量,占據獨立的內存,所以上下文進程間的切換開銷(棧、寄存器、虛擬內存、文件句柄等)比較大,但相對比較穩定安全。
  • 線程: 線程是進程的一個實體,線程是內核態,而且是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。線程間通信主要通過共享內存,上下文切換很快,資源開銷較少,但相比進程不夠穩定容易丟失數據。

****協程: 協程是一種用戶態的輕量級線程,協程的調度完全是由用戶來控制的。協程擁有自己的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其他地方,在切回來的時候,恢復先前保存的寄存器上下文和棧,直接操作棧則基本沒有內核切換的開銷,可以不加鎖的訪問全局變量,所以上下文的切換非常快。

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

通常小對象過多會導致GC三色法消耗過多的GPU。優化思路是,減少對象分配。

channel 為什么它可以做到線程安全?

Channel 可以理解是一個先進先出的隊列,通過管道進行通信,發送一個數據到Channel和從Channel接收一個數據都是原子性的。不要通過共享內存來通信,而是通過通信來共享內存,前者就是傳統的加鎖,后者就是Channel。設計Channel的主要目的就是在多任務間傳遞數據的,本身就是安全的。

GC 的觸發條件?

主動觸發(手動觸發),通過調用 runtime.GC 來觸發GC,此調用阻塞式地等待當前GC運行完畢。
被動觸發,分為兩種方式:

  • 使用步調(Pacing)算法,其核心思想是控制內存增長的比例,每次內存分配時檢查當前內存分配量是否已達到閾值(環境變量GOGC):默認100%,即當內存擴大一倍時啟用GC。
  • 使用系統監控,當超過兩分鐘沒有產生任何GC時,強制觸發 GC。

怎么查看Goroutine的數量?怎么限制Goroutine的數量?

  • 在Golang中,GOMAXPROCS中控制的是未被阻塞的所有Goroutine,可以被 Multiplex 到多少個線程上運行,通過GOMAXPROCS可以查看Goroutine的數量。
  • 使用通道。每次執行的go之前向通道寫入值,直到通道滿的時候就阻塞了。

Channel是同步的還是異步的?

Channel是異步進行的, channel存在3種狀態:

  • nil,未初始化的狀態,只進行了聲明,或者手動賦值為nil
  • active,正常的channel,可讀或者可寫
  • closed,已關閉,千萬不要誤認為關閉channel后,channel的值是nil

操作

一個零值nil通道

一個非零值但已關閉的通道

一個非零值且尚未關閉的通道

關閉

產生恐慌

產生恐慌

成功關閉

發送數據

永久阻塞

產生恐慌

阻塞或者成功發送

接收數據

永久阻塞

永不阻塞

阻塞或者成功接收

Goroutine和線程的區別?

  • 一個線程可以有多個協程
  • 線程、進程都是同步機制,而協程是異步
  • 協程可以保留上一次調用時的狀態,當過程重入時,相當于進入了上一次的調用狀態
  • 協程是需要線程來承載運行的,所以協程并不能取代線程,「線程是被分割的CPU資源,協程是組織好的代碼流程」

Go的Struct能不能比較?

  • 相同struct類型的可以比較
  • 不同struct類型的不可以比較,編譯都不過,類型不匹配

Go主協程如何等其余協程完再操作?

使用sync.WaitGroup。WaitGroup,就是用來等待一組操作完成的。WaitGroup內部實現了一個計數器,用來記錄未完成的操作個數。Add()用來添加計數;Done()用來在操作結束時調用,使計數減一;Wait()用來等待所有的操作結束,即計數變為0,該函數會在計數不為0時等待,在計數為0時立即返回。

Go的Slice如何擴容?

slice 實現原理

在使用 append 向 slice 追加元素時,若 slice 空間不足則會發生擴容,擴容會重新分配一塊更大的內存,將原 slice 拷貝到新 slice ,然后返回新 slice。擴容后再將數據追加進去。

擴容操作只對容量,擴容后的 slice 長度不變,容量變化規則如下:

  • 若 slice 容量小于1024個元素,那么擴容的時候slice的cap就翻番,乘以2;一旦元素個數超過1024個元素,增長因子就變成1.25,即每次增加原來容量的四分之一。
  • 若 slice 容量夠用,則將新元素追加進去,slice.len++,返回原 slice
  • 若 slice 容量不夠用,將 slice 先擴容,擴容得到新 slice,將新元素追加進新 slice,slice.len++,返回新 slice。

Go中的map如何實現順序讀取?

Go中map如果要實現順序讀取的話,可以先把map中的key,通過sort包排序。

Go值接收者和指針接收者的區別?

究竟在什么情況下才使用指針?

參數傳遞中,值、引用及指針之間的區別!

方法的接收者:

  • 值類型,既可以調用值接收者的方法,也可以調用指針接收者的方法;
  • 指針類型,既可以調用指針接收者的方法,也可以調用值接收者的方法。

但是接口的實現,值類型接收者和指針類型接收者不一樣:

  • 以值類型接收者實現接口,類型本身和該類型的指針類型,都實現了該接口;
  • 以指針類型接收者實現接口,只有對應的指針類型才被認為實現了接口。

通常我們使用指針作為方法的接收者的理由:

  • 使用指針方法能夠修改接收者指向的值。
  • 可以避免在每次調用方法時復制該值,在值的類型為大型結構體時,這樣做會更加高效。

在Go函數中為什么會發生內存泄露?

Goroutine 需要維護執行用戶代碼的上下文信息,在運行過程中需要消耗一定的內存來保存這類信息,如果一個程序持續不斷地產生新的 goroutine,且不結束已經創建的 goroutine 并復用這部分內存,就會造成內存泄漏的現象。

Goroutine發生了泄漏如何檢測?

可以通過Go自帶的工具pprof或者使用Gops去檢測診斷當前在系統上運行的Go進程的占用的資源。

Go中兩個Nil可能不相等嗎?

Go中兩個Nil可能不相等。

接口(interface) 是對非接口值(例如指針,struct等)的封裝,內部實現包含 2 個字段,類型 T 和 值 V。一個接口等于 nil,當且僅當 T 和 V 處于 unset 狀態(T=nil,V is unset)。

兩個接口值比較時,會先比較 T,再比較 V。接口值與非接口值比較時,會先將非接口值嘗試轉換為接口值,再比較。

func main() {
 var p *int = nil
 var i interface{} = p
 fmt.Println(i == p) // true
 fmt.Println(p == nil) // true
 fmt.Println(i == nil) // false
}
  • 例子中,將一個nil非接口值p賦值給接口i,此時,i的內部字段為(T=*int, V=nil),i與p作比較時,將 p 轉換為接口后再比較,因此 i == p,p 與 nil 比較,直接比較值,所以 p == nil。
  • 但是當 i 與nil比較時,會將nil轉換為接口(T=nil, V=nil),與i(T=*int, V=nil)不相等,因此 i != nil。因此 V 為 nil ,但 T 不為 nil 的接口不等于 nil。

Go語言函數傳參是值類型還是引用類型?

  • 在Go語言中只存在值傳遞,要么是值的副本,要么是指針的副本。無論是值類型的變量還是引用類型的變量亦或是指針類型的變量作為參數傳遞都會發生值拷貝,開辟新的內存空間。
  • 另外值傳遞、引用傳遞和值類型、引用類型是兩個不同的概念,不要混淆了。引用類型作為變量傳遞可以影響到函數外部是因為發生值拷貝后新舊變量指向了相同的內存地址。

Go語言中的內存對齊了解嗎?

CPU 訪問內存時,并不是逐個字節訪問,而是以字長(word size)為單位訪問。比如 32 位的 CPU ,字長為 4 字節,那么 CPU 訪問內存的單位也是 4 字節。

CPU 始終以字長訪問內存,如果不進行內存對齊,很可能增加 CPU 訪問內存的次數,例如:

GO編程:小技巧

 

變量 a、b 各占據 3 字節的空間,內存對齊后,a、b 占據 4 字節空間,CPU 讀取 b 變量的值只需要進行一次內存訪問。如果不進行內存對齊,CPU 讀取 b 變量的值需要進行 2 次內存訪問。第一次訪問得到 b 變量的第 1 個字節,第二次訪問得到 b 變量的后兩個字節。

也可以看到,內存對齊對實現變量的原子性操作也是有好處的,每次內存訪問是原子的,如果變量的大小不超過字長,那么內存對齊后,對該變量的訪問就是原子的,這個特性在并發場景下至關重要。

簡言之:合理的內存對齊可以提高內存讀寫的性能,并且便于實現變量操作的原子性。

兩個 interface 可以比較嗎?

  • 判斷類型是否一樣

reflect.TypeOf(a).Kind() == reflect.TypeOf(b).Kind()

  • 判斷兩個interface{}是否相等

reflect.DeepEqual(a, b interface{})

  • 將一個interface{}賦值給另一個interface{}

reflect.ValueOf(a).Elem().Set(reflect.ValueOf(b))

go 打印時 %v %+v %#v 的區別?

  • %v 只輸出所有的值;
  • %+v 先輸出字段名字,再輸出該字段的值;
  • %#v 先輸出結構體名字值,再輸出結構體(字段名字+字段的值);
package main
import "fmt"

type student struct {
 id   int32
 name string
}

func main() {
 a := &student{id: 1, name: "微客鳥窩"}

 fmt.Printf("a=%v n", a) // a=&{1 微客鳥窩} 
 fmt.Printf("a=%+v n", a) // a=&{id:1 name:微客鳥窩} 
 fmt.Printf("a=%#v n", a) // a=&main.student{id:1, name:"微客鳥窩"}
}

什么是 rune 類型?

Go語言的字符有以下兩種:

  • uint8 類型,或者叫 byte 型,代表了 ASCII 碼的一個字符。
  • rune 類型,代表一個 UTF-8 字符,當需要處理中文、日文或者其他復合字符時,則需要用到 rune 類型。rune 類型等價于 int32 類型。
package main
import "fmt"

func main() {
    var str = "hello 你好" //思考下 len(str) 的長度是多少?

    //golang中string底層是通過byte數組實現的,直接求len 實際是在按字節長度計算  
    //所以一個漢字占3個字節算了3個長度
    fmt.Println("len(str):", len(str))  // len(str): 12

    //通過rune類型處理unicode字符
    fmt.Println("rune:", len([]rune(str))) //rune: 8
}

空 struct{} 占用空間么?

可以使用 unsafe.Sizeof 計算出一個數據類型實例需要占用的字節數:

package main

import (
 "fmt"
 "unsafe"
)

func main() {
 fmt.Println(unsafe.Sizeof(struct{}{}))  //0
}

空結構體 struct{} 實例不占據任何的內存空間。

空 struct{} 的用途?

因為空結構體不占據內存空間,因此被廣泛作為各種場景下的占位符使用。

  1. 將 map 作為集合(Set)使用時,可以將值類型定義為空結構體,僅作為占位符使用即可。

type Set map[string]struct{}

func (s Set) Has(key string) bool {
 _, ok := s[key]
 return ok
}

func (s Set) Add(key string) {
 s[key] = struct{}{}
}

func (s Set) Delete(key string) {
 delete(s, key)
}

func main() {
 s := make(Set)
 s.Add("Tom")
 s.Add("Sam")
 fmt.Println(s.Has("Tom"))
 fmt.Println(s.Has("Jack"))
}

不發送數據的信道(channel)
使用 channel 不需要發送任何的數據,只用來通知子協程(goroutine)執行任務,或只用來控制協程并發度。


func worker(ch chan struct{}) {
 <-ch
 fmt.Println("do something")
 close(ch)
}

func main() {
 ch := make(chan struct{})
 go worker(ch)
 ch <- struct{}{}
}

結構體只包含方法,不包含任何的字段

type Door struct{}

func (d Door) Open() {
 fmt.Println("Open the door")
}

func (d Door) Close() {
 fmt.Println("Close the door")
}

分享到:
標簽:編程
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定