Effective Go

goroutine部分

goroutine的一些tricks,比如

func Announce(message string, delay time.Duration) {
    go func() {
        time.Sleep(delay)
        fmt.Println(message)
    }()  // 注意括号 - 必须调用该函数。
}

直接在go关键字后面接一个lambada表达式作为例程。

goroutine通常和channal一起使用,Unix的管道是基于生产-消费者模型,而channal则使用CSP(Communicating Sequential Process)进行构建。信道没有数据的时候会进行阻塞,利用这种条件可以实现一些信号量机制。

var sem = make(chan int, MaxOutstanding)

func handle(r *Request) {
    sem <- 1 // 等待活动队列清空。
    process(r)  // 可能需要很长时间。
    <-sem    // 完成;使下一个请求可以运行。
}

func Serve(queue chan *Request) {
    for {
        req := <-queue
        go handle(req)  // 无需等待 handle 结束。
    }
}

例如这样一段代码可以实现最大接受请求数量为MaxOutstanding,当新的请求到达时,req := <-queue从阻塞中恢复并且执行goroutine处理请求,再往sem里面写入内容时,会因为队列满了而阻塞,当然这样也有局限性,当有大量请求到达的时候,会不停地新生成新的goroutine,占用系统资源。

func Serve(queue chan *Request) {
    for req := range queue {
        req := req // 为该Go程创建 req 的新实例。
        sem <- 1
        go func() {
            process(req)
            <-sem
        }()
    }
}

解决方案是在循环的routine中尝试往信道中写入内容,这样可以正确实现队列的大小限制。考虑去掉req := req这一行,req变量在每个循环中都被赋予不同的值,但是实际上底层使用的同样的内存,相应的goroutine后的函数闭包可以引用该作用域的变量并且保持和修改,所以每个新生成的goroutine都会使用同一个变量,造成比较严重的错误。

Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once.

另外,并发(concurrency)并行(parallelism)是两种单独的意思,并发是多个独立地执行程序的组合,即一次性解决大量的事情,而并行是同时执行某些相关连的计算。

反射相关

变量的最基本信息就是类型和值:反射包的 Type 用来表示一个 Go 类型,反射包的 Value 为 Go 值提供了反射接口。这对于没有源代码的包尤其有用。这是一个强大的工具,除非真得有必要,否则应当避免使用或小心使用。

实际上,反射是通过检查一个接口的值,变量首先被转换成空接口。这从下面两个函数签名能够很明显的看出来:

func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value

接口的值包含一个 type 和 value。

结构体,集合设计

https://go.fdos.me/11.14.html (golang中一些关于结构体的设计技巧)

常见错误

50种常见错误 (在awesome-go仓库里面翻到的,有空可以看看)

翻译版本

并发控制库

Context库 (该作者其他的帖子也可以看看,干货较多)