Effective Go 小记

Effective Go

  • 指针和值的方法有所不同。针对值的方法,可以作用于值和指针;针对指针的方法,只能作用于指针。
    主要的不同是 值的方法无法修改值本身,而指针可以
    例外:当对值调用原本作用于指针的方法时,如果值可以取地址,则编译器会帮忙转换。

    type Size int
    func (s *Size) Add(a Size) {}
    
    var s Size
    s.Add(1) // 等价于 (&s).Add(1),此处编译器帮忙进行了转换
    
  • 类型转换 可以帮忙程序调用正确的方法

  • 接口转换(interface conversion) vs 类型断言(type assertion)

    type Stringer interface {
        String() string
    }
    
    var value interface{} // Value provided by caller.
    switch str := value.(type) {
    case string:
        return str
    case Stringer:
        return str.String()
    }
    

    类型断言可以帮忙把value中的指定类型成员拿出来使用,如果没有,则会抛runtime err或将ok置false

    if str, ok := value.(string); ok {  // 尝试类型断言
        return str
    } else if str, ok := value.(Stringer); ok { //
        return str.String()
    }
    
  • 除了指针和接口,方法可以针对任何类型定义,甚至可以 为函数类型定义方法

    type Handler interface {
        ServeHTTP(ResponseWriter, *Request)
    }
    
    // The HandlerFunc type is an adapter to allow the use of
    // ordinary functions as HTTP handlers.  If f is a function
    // with the appropriate signature, HandlerFunc(f) is a
    // Handler object that calls f.
    type HandlerFunc func(ResponseWriter, *Request)
    
    // ServeHTTP calls f(w, req).
    func (f HandlerFunc) ServeHTTP(w ResponseWriter, req *Request) {
        f(w, req)
    }
    
    // Argument server.
    func ArgServer(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintln(w, os.Args)
    }
    
    http.Handle("/args", http.HandlerFunc(ArgServer))
    
  • _可以用来将无用的变量赋值给它。大多是为了避免编译错误。

    • 有些变量暂时用不到
      package main
      
      import (
          "fmt"
          "io"
          "log"
          "os"
      )
      
      var _ = fmt.Printf // For debugging; delete when done.
      var _ io.Reader    // For debugging; delete when done.
      
      func main() {
          fd, err := os.Open("test.go")
          if err != nil {
              log.Fatal(err)
          }
          // TODO: use fd.
          _ = fd
      }
      
    • 有些import包,只是想要它的副作用,而不会真正使用它
      import _ "net/http/pprof"
      
    • 有时候想在编译期检查类型转换是否ok
      var _ json.Marshaler = (*RawMessage)(nil)
      
    • 判断value是否实现了该接口
      if _, ok := val.(json.Marshaler); ok {
        fmt.Printf("value %v of type %T implements json.Marshaler\n", val, val)
      }
      
  • 内嵌type或*type可以让类具有type相同的方法,当然也可以自行定义struct的同名方法起到覆盖的作用。 多个内嵌类型方法/成员同名冲突的解决规则:

    • 越表层的方法/成员 掩盖 越底层(深度)的方法/成员
    • 当外部从未访问过该方法/成员,同名冲突可以直接忽略,否则会报错

Concurrency

Do not communicate by sharing memory; instead, share memory by communicating. 不要通过共享内存来通信;通过通信来共享内存。

goroutine: 在相同地址空间与其他goroutine一起并发执行的函数。(与线程、协程、进程的概念区分开)

goroutine 轻量 ,开销基本就是栈空间的分配。栈空间初始很小,所以分配开销小,当需要时通过分配堆存储来增长。

goroutine 与 OS线程是 多对多映射 ,当发生阻塞(等待IO)时,其他goroutine继续运行。其中隐藏掉了很多线程创建和管理的复杂操作。

go中的匿名函数是 闭包 ,函数中涉及到的变量会在函数执行过程中一直存活。

go闭包错误示例:

func Serve(queue chan *Request) {
    for req := range queue {
        sem <- 1
        go func() {
            process(req) // Buggy; see explanation below.
            <-sem
        }()
    }
}
// req被循环的多轮共享,所以多个goroutine处理了相同的req

更正为:

func Serve(queue chan *Request) {
    for req := range queue {
        sem <- 1
        go func(req *Request) {
            process(req)
            <-sem
        }(req)
    }
}

或者

func Serve(queue chan *Request) {
    for req := range queue {
        req := req // Create new instance of req for the goroutine.
        sem <- 1
        go func() {
            process(req)
            <-sem
        }()
    }
}
// req := req 可以遮挡循环本身的req,这样每个goroutine会获得不同的req

如果想起到控制并发度的效果,也可以直接拉起固定并发的若干goroutine,然后所有goroutine共享同一channel

func handle(queue chan *Request) {
    for r := range queue {
        process(r)
    }
}

func Serve(clientRequests chan *Request, quit chan bool) {
    // Start handlers
    for i := 0; i < MaxOutstanding; i++ {
        go handle(clientRequests)
    }
    <-quit  // Wait to be told to exit.
}
  • channel也可以是channel的元素, 比如chan chan int😂

  • Parallelization

runtime.NumCPU()可以告知CPU数目 runtime.GOMAXPROCS(0) 返回默认的工作线程数,缺省值为NumCPU(),可以手动设置

Panic Recover

Recover可以用来将模块内部的panic转化为普通的error。如果此处是其他panic,代码中会在 type assert时触发runtime error,导致程序停止。

// Error is the type of a parse error; it satisfies the error interface.
type Error string
func (e Error) Error() string {
    return string(e)
}

// error is a method of *Regexp that reports parsing errors by
// panicking with an Error.
func (regexp *Regexp) error(err string) {
    panic(Error(err))
}

// Compile returns a parsed representation of the regular expression.
func Compile(str string) (regexp *Regexp, err error) {
    regexp = new(Regexp)
    // doParse will panic if there is a parse error.
    defer func() {
        if e := recover(); e != nil {
            regexp = nil    // Clear return value.
            err = e.(Error) // Will re-panic if not a parse error.
        }
    }()
    return regexp.doParse(str), nil
}