第十一章

回到电脑, Erlang的作者Armstrong在其博士论文中证明, 单个机器自身, 由于对其硬件出错的不可挽回, 本质上是不可靠的. 但独立机器构成的网络, 可以互相监督和备份, 在容错系统软件的支持下, 完全有能力大大提高整体的可靠性到九个9, 也就是理论上几乎完全可靠.

Erlang是个出色的并发编程语言. Golang在设计上也是很出色的并发编程语言. 假以时日, Go一定可以甚至超过Erlang的完善程度. 因为, 其一, Go是编译执行, 比靠虚拟机操作的Erlang要快很多. 其二, Go的语法更接近主流的C, 远比Prolog逻辑语言出身的Erlang有更多的程序员.

并发和容错的是一个问题的两面. 多机并发保证可靠的出错处理, 而可靠的出错处理机制是成功并发的关键.

在Go语言里, go func 是并发的单元. chan 是协调并发单元的机制. panic和recover是出错处理的机制. 而defer是神来之笔, 大大简化了出错的管理.

先看看go func. 她本来也是个普通的可以func. 例如一个最简单的例子:

package main
func f(){}
func main(){
    defer f()
    go f()
    f()    
}

$ 6l -S go.go

--- prog list "f" ---
0000 (go.go:2) TEXT    f+0(SB),$0-0
0001 (go.go:2) RET     ,

--- prog list "main" ---
0002 (go.go:3) TEXT    main+0(SB),$0-0
0003 (go.go:3) JMP     ,5
0004 (go.go:3) JMP     ,18
0005 (go.go:4) PUSHQ   $f+0(SB),
0006 (go.go:4) PUSHQ   $0,
0007 (go.go:4) CALL    ,runtime.deferproc+0(SB)
0008 (go.go:4) POPQ    ,CX
0009 (go.go:4) POPQ    ,CX
0010 (go.go:4) TESTQ   AX,AX
0011 (go.go:4) JNE     ,4
0012 (go.go:5) PUSHQ   $f+0(SB),
0013 (go.go:5) PUSHQ   $0,
0014 (go.go:5) CALL    ,runtime.newproc+0(SB)
0015 (go.go:5) POPQ    ,CX
0016 (go.go:5) POPQ    ,CX
0017 (go.go:6) CALL    ,f+0(SB)
0018 (<epoch>) CALL    ,runtime.deferreturn+0(SB)
0019 (<epoch>) RET     ,

f()很普通, 可以被直接func, 也可被go func, 还可以被defer func. 从汇编码我们清楚的看到, 第17行一个简单的CALL呼叫可以就地func. 而第12行到第16行的go func则有些学问.

编译器安排好了一些前戏, 把被func的地址, 就是$f, 和其口袋要放的钱数, PUSHQ存好, 然后叫来runtime.newproc这个家伙. 他拿着地址和钱给f()一个新房间, 让你们不受干扰的好好func. 完后, 编译器再安排两个POPQ打扫干净. 由于有了go func, 除了在 main 大厅外, Go还可以轻易的安排非常多的小房间, 和main大厅里的同时func. 这种互不打扰的群体func行为史称并发.

再看看defer. 类似go func, 第5和第6行放好地址和钱, 第7行叫来了runtime.deferproc. runtime就是龟婆, 提供func执行的环境. defer是延迟的意思好似伟哥, 她把本来要马上发生的f()行为推迟到最后发生. 也就是RET前的第18行. deferproc过后, 除了例常的POPQ清洗, 还有第10行的TESTQ感染检查. 如果AX是0(第一次还是干净的), 则继续, 直到最后执行runtime.deferreturn. 这个家伙可不简单, 他和deferproc还有go都是Ken神的杰作. deferproc记下的地址和钱被deferreturn使用, f()被叫来defer func. 但和go func不同, 不需给他们分配新房间. 因为, 正在执行的 main func 快要结束了, 只需简单的整理一下床褥就可以让f()继续使用了. 这就是deferreturn的工作. 具体的可以看看Russ博客.

defer有什么用? 最常见的是收尾的工作, 如, 打开的文件要关闭, 分配的资源要释放, 堰塞的精液要发泄, 这些都是defer常日的工作. 但她和recover的配合更是如双飞般曼妙.

panic和recover是Rob的杰作. 和go与defer不同, 她们没在Go的25把钥匙里所以她们不需要由编译器安排工作. 她们和make一样是runtime的内建函数. 同样的还有append和copy用来添加和拷贝切片, len和cap用来得到切片和字串的长度以及切片的容量. new和make一样分配和制作一个新类型的变量但new返回其地址而make返回变量本身. 还有Ken的cmplx, real, imag用来组装和分解复数值.

panic就像二奶一样见光死. 我拍你给组织看. 你说笑话谁还怕组织, 裆萎早准备好了我的recover, 还有个defer等着收拾你这类不知死活的举报者. 例如:

package main
import "fmt"

func main(){
    p()
    fmt.Println("安全回家")
}
func p(){
    defer func() {
        if r:= recover(); r != nil {
            fmt.Println("组织照顾, 票数为", r)
        }
    }()

    fmt.Println("叫基")
    j(0)
    fmt.Println("不可能出事儿")
}

func j(i int){
    if i > 6 {
        fmt.Println("太累了, 趴下吧")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("清理基数", i)
    fmt.Println("已用基数", i)
    j(i+1)
}

先想想整个社会是怎么啦, 再去golang的操场验证一下吧.