Go - arthur791004/notes GitHub Wiki

Keypoint

  • libtask
    • 比線程更小的線程, 但卻不是線程, 所以不需要 thread context switch 的 overhead
  • libevent
    • non-blocking socket, buffered IO
  • openssl
    • SSL, encrypt/decrypt, hash, 以往 C 裡要用這些func其實是非常蛋疼的
  • cross-compiling
    • win32, linux, armv7, aarch64...
  • 本身沒有 memory leak, 有 GC, 寫法很簡單

Problem of C

Basic

  • Package: 套件名稱
    package main
    
  • Import: 引用 library
    import "fmt"
    // or
    import (
      "fmt"
    )
    
  • Main: 程式運行的入口
    func main() {
      fmt.Println("Hello")
    }
    
  • Export Name: Library export 的 function 名稱必須為 大寫 開頭
  • Function
    func add(x int, y int) int {
      return x + y
    }
    // 多值返回
    func swap(x, y string) (string, string) {
      return y, x
    }
    
    func main() {
      a, b := swap(1, 2)
    }
    
    // 命名返回值
    func split(sum int) (x, y int) {
      x = sum * 4 / 9
      y = sum - x
      return
    }
    
  • 變數宣告
    • var, const
    • const 不能使用 := 語法定義。
  • 短聲明變數
    • 在函式中, := 簡潔賦值語句在明確類型的地方,可以用於替代 var 定義。
    • 函式外的每個語法塊都必須以關鍵字開始( varfunc 、等等), := 結構不能使用在函式外。
  • basic types
    bool
    string
    
    int  int8  int16  int32  int64
    uint uint8 uint16 uint32 uint64 uintptr
    
    byte // uint8 的別名
    
    rune // int32 的別名, 代表一個Unicode碼
    
    float32 float64
    
    complex64 complex128
    
  • loop
    // for
    func main() {
      sum := 0
      for i := 0; i < 10; i++ {
        sum += i
      }
      fmt.Println(sum)
    }
    
    // while
    func main() {
      sum := 1
      for sum < 1000 {
        sum += sum
      }
      fmt.Println(sum)
    }
    
  • if
    // 便捷語句定義: v 作用域僅在 if 和 else 範圍之內
    if v := math.Pow(x, n); v < lim {
      return v
    } else {
      fmt.Printf("%g >= %g\n", v, lim)
    }
    
  • switch: 除非以 fallthrough 語句結束,否則分支會自動終止。
    func main() {
      switch os := runtime.GOOS; os {
        case "darwin":
          fmt.Println("OS X.")
        case "linux":
          fmt.Println("Linux.")
        default:
          fmt.Printf("%s.", os)
      }
    }
    
  • struct
    type Vertex struct {
      X int
      Y int
    }
    
    func main() {
      v := Vertex{1, 2}
      v.X = 4
      fmt.Println(v.X)
    }
    
  • 指針: Go有指針,但是沒有指針運算。
    type Vertex struct {
      X int
      Y int
    }
    
    func main() {
      p := Vertex{1, 2}
      q := &p
      q.X = 1e9
      fmt.Println(p)
    }
    
  • new 函式: 表達式 new(T) 分配了一個零初始化的 T 值,並返回指向它的指針。
    var t *T = new(T)
    // or
    t := new(T)
    
  • 陣列: 類型 [n]T 是一個有 n 個類型為 T 的值的陣列
    var a [10]int
    p := []int{2, 3, 5, 7, 11, 13}
    q := p[1:4]
    
  • 構造 slice
    // slice 由函式 make 創建。這會分配一個零長度的陣列並且返回一個 slice 指向這個陣列
    a := make([]int, 5)  // len(a)=5
    
    // 為了指定容量,可傳遞第三個參數到 make
    b := make([]int, 0, 5) // len(b)=0, cap(b)=5
    
  • 空 slice: slice 的零值是 nil
  • range
    var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
    
    func main() {
      for i, v := range pow {
        fmt.Printf("2**%d = %d\n", i, v)
      }
    }
    
    /*
     * output
     * 2**0 = 1
     * 2**1 = 2
     * 2**2 = 4
     * 2**3 = 8
     * 2**4 = 16
     */
    
  • map
    • map 映射鍵到值。
    • map 在使用之前必須用 make 而不是 new 來創建;值為 nil 的 map 是空的,並且不能賦值
    // example 1
    var m map[string]Vertex
    
    func main() {
      m = make(map[string]Vertex)
      m["Bell Labs"] = Vertex{
        40.68433, -74.39967,
      }
      fmt.Println(m["Bell Labs"])
    }
    
    // example 2
    var m = map[string]Vertex{
      "Bell Labs": Vertex{
        40.68433, -74.39967,
      },
      "Google": Vertex{
        37.42202, -122.08408,
      },
    }
    
    // exmaple 3: 如果頂級的類型只有類型名的話,可以在文法的元素中省略鍵名。
    var m = map[string]Vertex{
      "Bell Labs": {40.68433, -74.39967},
      "Google":    {37.42202, -122.08408},
    }
    
    // 在 map m 中插入或修改一個元素:
    m[key] = elem
    
    // 獲得元素:
    elem = m[key]
    
    // 刪除元素:
    delete(m, key)
    
    // 通過雙賦值檢測某個鍵存在:
    // 如果 key 在 m 中, ok 為 true 。否則為 false ,並且 elem 是 map 的元素類型的零值。
    elem, ok = m[key]
    
  • 函式為值
    func main() {
      hypot := func(x, y float64) float64 {
        return math.Sqrt(x*x + y*y)
      }
    
      fmt.Println(hypot(3, 4))
    }
    
  • 函式的閉包: Go 函式可以是閉包的。閉包是一個函式值,它來自函式體的外部的變數引用。函式可以對這個引用值進行訪問和賦值
    func adder() func(int) int {
      sum := 0
      return func(x int) int {
        sum += x
        return sum
      }
    }
    
    func main() {
      pos, neg := adder(), adder()
      for i := 0; i < 10; i++ {
        fmt.Println(
          pos(i),
          neg(-2*i),
        )
      }
    }
    
  • Method: Go 沒有類。然而,仍然可以在結構體類型上定義方法。
    type Vertex struct {
      X, Y float64
    }
    
    func (v *Vertex) Abs() float64 {
      return math.Sqrt(v.X*v.X + v.Y*v.Y)
    }
    
    func main() {
      v := &Vertex{3, 4}
      fmt.Println(v.Abs())
    }
    
  • 接收者為指針的方法: 方法可以與命名類型或命名類型的指針關聯。
    • 有兩個原因需要使用指針接收者:
      • 首先避免在每個方法調用中拷貝值(如果值類型是大的結構體的話會更有效率)
      • 其次,方法可以修改接收者指向的值。
    • pass by value or pass by reference
    type Vertex struct {
      X, Y float64
    }
    
    /*
     * Important
     * 如果使用 pass by value, 即 func (v Vertex), 則 Scale 沒任何作用,並不會修改到原本 v 所指到的位置的值
     */
    func (v *Vertex) Scale(f float64) {
      v.X = v.X * f
      v.Y = v.Y * f
    }
    
    func (v *Vertex) Abs() float64 {
      return math.Sqrt(v.X*v.X + v.Y*v.Y)
    }
    
    func main() {
      v := &Vertex{3, 4}
      v.Scale(5)
      fmt.Println(v, v.Abs())
    }
    
  • 介面
    • 介面類型是由一組方法定義的集合。
    • 介面類型的值可以存放實現這些方法的任何值。
    type Abser interface {
      Abs() float64
    }
    
    func main() {
      var a Abser
      f := MyFloat(-math.Sqrt2)
      v := Vertex{3, 4}
    
      a = f  // a MyFloat implements Abser
      a = &v // a *Vertex implements Abser
    
      // In the following line, v is a Vertex (not *Vertex)
      // and does NOT implement Abser.
      a = v
    
      fmt.Println(a.Abs())
    }
    
    type MyFloat float64
    
    func (f MyFloat) Abs() float64 {
      if f < 0 {
        return float64(-f)
      }
      return float64(f)
    }
    
    type Vertex struct {
      X, Y float64
    }
    
    func (v *Vertex) Abs() float64 {
      return math.Sqrt(v.X*v.X + v.Y*v.Y)
    }
    
  • 隱式介面
    • 類型通過實現那些方法來實現介面。
    • 沒有顯式聲明的必要。
    • 隱式介面解藕了實現介面的包和定義介面的包:互不依賴。
    • 因此,也就無需在每一個實現上增加新的介面名稱,這樣同時也鼓勵了明確的介面定義。
    type Reader interface {
      Read(b []byte) (n int, err error)
    }
    
    type Writer interface {
      Write(b []byte) (n int, err error)
    }
    
    type ReadWriter interface {
      Reader
      Writer
    }
    
    func main() {
      var w Writer
    
      // os.Stdout implements Writer
      w = os.Stdout
    
      fmt.Fprintf(w, "hello, writer\n")
    }
    
  • 錯誤
    type MyError struct {
      When time.Time
      What string
    }
    
    func (e *MyError) Error() string {
      return fmt.Sprintf("at %v, %s", e.When, e.What)
    }
    
    func run() error {
      return &MyError{time.Now(), "it didn't work"}
    }
    
    func main() {
      if err := run(); err != nil {
        fmt.Println(err)
      }
    }
    
  • Web 服務器
    • 包 http 通過任何實現了 http.Handler 的值來響應 HTTP 請求:
      package http
      
      type Handler interface {
        ServeHTTP(w ResponseWriter, r *Request)
      }
      
    • example
      package main
      
      import (
        "fmt"
        "net/http"
      )
      
      type Hello struct{}
      
      func (h Hello) ServeHTTP(
        w http.ResponseWriter,
        r *http.Request) {
        fmt.Fprint(w, "Hello!")
      }
      
      func main() {
        var h Hello
        http.ListenAndServe("localhost:4000", h)
      }
      
  • HTTP 處理: 在 web 服務器中註冊它們來處理指定的路徑。
    http.Handle("/string", String("I'm a frayed knot."))
    http.Handle("/struct", &Struct{"Hello", ":", "Gophers!"})
    

Go Routine

  • 是由Go運行時環境管理的輕量級線程。
    go f(x, y, z)
    
  • 開啟一個新的 goroutine 執行
    f(x, y, z)
    
  • goroutine 在相同的地址空間中運行,因此訪問共享記憶體必須進行同步。sync 提供了這種可能,不過在Go中並不經常用到,因為有其他的辦法。
  • Example
    func say(s string) {
      for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
      }
    }
    
    func main() {
      go say("world")
      say("hello")
    }
    

Go Channel

  • channel是有類型的管道,可以用channel操作符 <- 對其發送或者接收值。
    • 「箭頭」就是數據流的方向。
    // 和 map 與 slice 一樣,channel 使用前必須創建:
    ch := make(chan int)
    
    ch <- v    // 將 v 送入 channel ch。
    v := <-ch  // 從 ch 接收,並且賦值給 v。
    
  • 默認情況下,在另一端準備好之前,發送和接收都會阻塞。這使得 goroutine 可以在沒有明確的鎖或競態變數的情況下進行同步。
  • Example
    func sum(a []int, c chan int) {
      sum := 0
      for _, v := range a {
        sum += v
      }
      c <- sum // send sum to c
    }
    
    func main() {
      a := []int{7, 2, 8, -9, 4, 0}
    
      c := make(chan int)
      go sum(a[:len(a)/2], c)
      go sum(a[len(a)/2:], c)
      x, y := <-c, <-c // receive from c
    
      fmt.Println(x, y, x+y)
    }
    
  • 緩衝 channel
    • channel 可以是 帶緩衝的
    • 為 make 提供第二個參數作為緩衝長度來初始化一個緩衝 channel:
      ch := make(chan int, 100)
      
    • 向緩衝 channel 發送數據的時候,只有在緩衝區滿的時候才會阻塞。當緩衝區清空的時候接受阻塞。
    func main() {
      c := make(chan int, 2)
      c <- 1
      c <- 2
      fmt.Println(<-c)
      fmt.Println(<-c)
    }
    
  • range 和 close
    • 發送者可以 close 一個 channel 來表示再沒有值會被發送了
    • 接收者可以通過賦值語句的第二參數來測試channel是否被關閉
      • 當沒有值可以接收並且 channel 已經被關閉,那麼 ok 會被設置回 false
      v, ok := <-ch
      
    • 循環 for i := range c 會不斷從channel接收值,直到它被關閉。
    • Note
      • 只有發送者才能關閉 channel,而不是接收者。
      • 向一個已經關閉的 channel 發送數據會引起 panic。
      • 還要注意: channel與文件不同;通常情況下無需關閉它們
        • 只有在需要告訴接收者沒有更多的數據的時候才有必要進行關閉,例如中斷一個 range
  • select
    • select 語句使得一個 goroutine 在多個通訊操作上等待
    • select 會阻塞,直到條件分支中的某個可以繼續執行,這時就會執行那個條件分支。當多個都準備好的時候,會隨機選擇一個。
    func fibonacci(c, quit chan int) {
      x, y := 0, 1
      for {
        select {
          // 把 x 丟到 channel, 當 channel 滿的時候會 block, 必須等到 channel 被清空(取值)後, 這個條件才能繼續執行
          case c <- x:
            x, y = y, x+y
          // 從 channel 取值, 必須等到 channel quit 有值, 這個條件才能執行
          case <-quit:
            fmt.Println("quit")
            return
        }
      }
    }
    
    func main() {
      c := make(chan int)
      quit := make(chan int)
      go func() {
        for i := 0; i < 10; i++ {
          fmt.Println(<-c)
        }
        quit <- 0
      }()
    
      fibonacci(c, quit)
    }
    
    • 當 select 中的其他條件分支都沒有準備好的時候,default 分支會被執行。
      • 為了非阻塞的發送或者接收,可使用 default 分支:
      select {
        case i := <-c:
          // 使用 i
        default:
          // 從 c 讀取會阻塞
      }
      

defer panic recover

深入理解 interface

深入理解 goroutine 和 channel

  • Concurrency v.s Parallelism
    • Concurrency: 併發處理,看起來像一起做,但其實是不斷的切換,實際上還是只有一個 process 在跑
      • 將 process 切割成多個可執行單位(coroutine)
      • 如果有多個 process 時則在 coroutine 中交互執行
      • 這樣不會因為某一個 process 執行太久造成其他 process 無法處理, 會讓你有多工的感覺.
      • 但這並不會增加執行效率. 總執行時間是一樣的
    • Parallelism: 平行處理,實際上會有 N 個 process 在跑
  • What is goroutine ?
    • 每一個併發的執行單元叫作一個 goroutine
  • Why efficiency ?
    • 在創造 goroutine 的資源消耗是很小的, 因為 goroutine 只是一個 function 的入口. 只要很小的 stack 去記錄即可.
    • thread 上的管理, 當某個 goroutine 被 block 住, 處理此 goroutine 的 thread 就耗在這上, 此時 run-time 會將其他的 goroutine 轉給此 thread 去執行, 這樣 thread 就不會因為執行某些被 block 的任務則消耗在那邊. 有效的使用 thread.
    • thread 最多不會超過 $GOMAXPROCS(使用者設定的), 可以確保 thread 不會失控的增加.
  • Communitaction between gorouting
    • Don't communicate by sharing memory; share memory by communicating.
    • goroutine 彼此的溝通方式是使用 channel (share memory by communicating)
    • channels
      • 需要一個 sender 和一個 receiver, 當 sender 和 receiver 都 ready 時候, 訊息才會成功的傳遞
      /*
       * example 1
       * 但是 code 無法跑, compiler 會顯示 dead lock.
       * 因為執行到 s <- "hello" 這步的時候, sender 就會進入 ready 狀態.
       * 然後就停住了, 也就是他不會執行到 val := <- s .
       */
      func main(){
        s := mack(chan string) //宣告一個 channel 變數
        s <- "hello"           //寫入 channel (sender)
        val <- s               //讀取 channel (receiver)
        fmt.Println(val)
      }
      
      /*
       * example 2
       * 建立一個 goroutine 去跑 s <- "hello"
       * 1. 建立一個 goroutine  // 此時 s <- "hello" 還沒執行
       * 2. 執行 val := <- s   // s 空的, receiver ready (停住)
       * 3. 執行 s <- "hello"  // sender 將訊息寫入 s, sender ready
       * 4. val := <- s       // 成功讀取 s, (因為 receiver, sender 都 ready)
       * 5. fmt.Println(val)
       */
      func main(){
        s := make(chan string) //宣告一個 channel 變數
        go func(){
          s <- "hello"           //寫入 channel (sender)
        }()
      
        val := <- s               //讀取 channel (receiver)
        fmt.Println(val)
      }
      

Reference