iota

可以按照特定的规则执行逻辑:

package main

import "fmt"

const (
    b = 1 << (10 * iota)
    kb
    mb
    gb
    tb
    pb
)

func main(){
    // 1 1024 1048576 1099511627776 1125899906842624
    fmt.Println(b,kb,mb,tb,pb)
}

go中的slice本身没有数据,是对底层array的view。

闭包

package main

import "fmt"

func adder() func (value int) int {
    sum:=0
    return func (value int)int  {
        sum += value
        return sum
    }    
}

func main()  {
    adder:=adder()
    for i := 0; i < 10; i++ {
        // 0 1 3 6 10 15 21 28 36 45
        fmt.Println(adder(i))
    }
}

以上的代码使用闭包保存了状态(有一个自由变量),正宗的函数式编程长这样:

package main

import "fmt"

type iAddr func (int) (int,iAddr)

func adder(base int) iAddr {
    return func (v int) (int,iAddr) {
        return base + v,adder(base + v)
    }
}

func main()  {
    add:=adder(0)
    for i := 0; i < 10; i++ {
        // 0 1 3 6 10 15 21 28 36 45
        var s int
        s,add = add(i)
        fmt.Println(s)
    }
}

常用的闭包例子:

// 斐波那契生成器
func fib() func() int {
    a,b := 0,1
    return func () int {
        a,b = b,a + b
        return a
    }
}

// 函数实现接口

package main

import (
    "bufio"
    "fmt"
    "io"
    "strings"
)

type intGen func() int

func fibonacci() intGen {
    a, b := 0, 1
    return func() int {
        a, b = b, a+b
        return a
    }
}

// 函数实现read接口
func (g intGen) Read(p []byte) (n int, err error) {
    next := g()
    if next > 10000 {
        return 0, io.EOF
    }
    s := fmt.Sprintf("%d\n", next)

    // TODO: incorrect if p is too small!
    return strings.NewReader(s).Read(p)
}

func printFileContents(reader io.Reader) {
    scanner := bufio.NewScanner(reader)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
}

func main() {
    var f intGen = fibonacci()
    printFileContents(f)
}

资源管理和错误处理

defer调用可以确保在函数结束的时候被调用,它是一个栈。

package main

import "fmt"

func main() {
    for i := 0; i < 10; i++ {
        defer fmt.Println(i)
        if (i == 5) {
            panic("print too many")
        }
    }
}

// $ go run main.go
// 5
// 4
// 3
// 2
// 1
// 0
// panic: print too many

recover可以在defer中调用,可以接收panic的值:

package main

import (
    "fmt"
    // "errors"
)

func tryRecover(){
    defer func(){
        r:= recover()
        // type assert
        if err, ok := r.(error);ok {
            fmt.Println("Error occured:",err)
        } else {
            panic(fmt.Sprintf("I dont't know what to do: %v", r))
        }
    }()

    // 输出:Error occured: this is an error
    // panic(errors.New("this is an error"))

     // 输出:Error occured: runtime error: integer divide by zero    
    // a:=5
    // b:=0
    // fmt.Println(a / b)

    // 输出:
    // panic: 123 [recovered]
    //     panic: I dont't know what to do: 123

    // goroutine 1 [running]:
    // main.tryRecover.func1()
    //         C:/Users/Administrator/Desktop/blog-source/source/main.go:15 +0x105
    // panic(0x4b0a20, 0x4ea298)
    //         c:/go/src/runtime/panic.go:969 +0x176
    // main.tryRecover()
    //         C:/Users/Administrator/Desktop/blog-source/source/main.go:27 +0x65
    // main.main()
    //         C:/Users/Administrator/Desktop/blog-source/source/main.go:31 +0x27
    // exit status 2
    // re panic : 因为123不是error类型
    panic(123)
}

func main() {
    tryRecover()
}

测试

go采用表格驱动测试:

image.png
image.png

分离了测试数据和测试逻辑。虽然其他语言也能做,但是go语言的语法使得表格测试更容易实现。因为表格实现起来比较容易。

goroutine & channel

io操作会切换协程,纯CPU操作不会进行协程的切换,这意味着如果一个协程不主动交出控制权的话就会一直占用CPU资源:

func main() {
    var a[100000] int
    for i := 0; i < 100000; i++ {
        go func(i int) {
            for {
                a[i]++
                // runtime.Gosched()
            }
        }(i)
    }
    time.Sleep(time.Millisecond)
    fmt.Println(a)
}

执行上面的代码后cpu会跑满,可以使用runtime.Gosched()可以手动交出控制权。

channel是goroutine和goroutine的交互,发了数据没人收的话是会死锁的!

func main()  {
    c:=make(chan int)

    // 将1,2送入channel
    c <- 1
    c <- 2

    // 从channel中读取
    n:= <-c
    fmt.Println(n)
}
// 报错:fatal error: all goroutines are asleep - deadlock!

正确的channel使用姿势应该长下面这样:

func main()  {
    c:=make(chan int)

    // 注意:这一段代码要放在向channel中输入数据之前
    go func ()  {
        for{
            n:= <-c
            // 可能只打印 1,2,3,4中的前几个,因为可能函数调用已经完成
            fmt.Println(n)
        }    
    }()

    // 将1,2送入channel
    c <- 1
    c <- 2
    c <- 3
    c <- 4
}

channel具有方向性,即可以使用<-或者->定义一个只能输入或者输出的channel:

func createWorker(id int) chan <- int {
    c := make(chan int)
    go func () {
        for n:= range(c){
            fmt.Printf("Worker %d received %c \n",id, n)
        }
    }()
    return c
}

func main()  {
    var channels [5]chan<- int
    for i := 0; i < 5; i++ {
        channels[i] = createWorker(i)
    }
    for i := 0; i < 5; i++ {
        // createWorker 方法创建的channel具有方向性,只能向channel中发数据,如果试图从channel中读取数据将会报错
        // n := <- channels[i]
        channels[i] <- 'a' + i
    }
    for i := 0; i < 5; i++ {
        channels[i] <- 'A' + i
    }
    time.Sleep(time.Millisecond)
}

// Worker 0 received a
// Worker 0 received A
// Worker 2 received c
// Worker 3 received d
// Worker 1 received b
// Worker 4 received e
// Worker 1 received B
// Worker 3 received D

make的第二个参数可以创建创建channel的缓冲区,对提升性能有帮助。

不要通过共享内存来通信,而是需要通过通信来共享内存。

上面的例子中由于我们开启了5个goroutine来并发执行,为了让goroutine有机会执行,最后进行了1ms的延迟,这样其实是非常不好的!因为我们不能确定程序能在1ms内全部处理完。对上面的代码稍加改造:

type worker struct{
    in chan int
    done chan bool
}

func doWork(id int,c chan int,done chan bool) {
    for n:= range(c){
        fmt.Printf("Worker %d received %c \n",id, n)
        done <- true
    }
}

func createWorker(id int) worker {
    w := worker{
        in: make(chan int),
        done: make(chan bool),
    }
    go doWork(id,w.in,w.done)
    return w
}

func main()  {
    var workers [5]worker
    for i := 0; i < 5; i++ {
        workers[i] = createWorker(i)
    }
    // 1个1个等
    for i,worker := range(workers) {
        worker.in <- 'a' + i
        <-worker.done
    }
    for i,worker := range(workers) {
        worker.in <- 'A' + i
        <-worker.done
    }
}

上面的代码会输出以下结果:

Worker 0 received a
Worker 1 received b
Worker 2 received c
Worker 3 received d
Worker 4 received e
Worker 0 received A
Worker 1 received B
Worker 2 received C
Worker 3 received D
Worker 4 received E

按顺序从小写字母打印到大写字母,程序是顺序执行。有没有什么办法提高程序并行度:

func main()  {
    var workers [5]worker
    for i := 0; i < 5; i++ {
        workers[i] = createWorker(i)
    }
    // 先并发打印小写
    for i,worker := range(workers) {
        worker.in <- 'a' + i
    }
    for _,worker := range(workers) {
        <-worker.done
    }
    // 再并发打印大写
    for i,worker := range(workers) {
        worker.in <- 'A' + i
    }
    for _,worker := range(workers) {
        <-worker.done
    }
}

上面的代码提高了并发度,小写字母并发打印,大写字母并发打印,这样一般来说也满足要求了,有没有方法所有任务一起并发?很自然会写出下面的代码:

func main()  {
    var workers [5]worker
    for i := 0; i < 5; i++ {
        workers[i] = createWorker(i)
    }
    for i,worker := range(workers) {
        worker.in <- 'a' + i
    }
    for i,worker := range(workers) {
        worker.in <- 'A' + i
    }
    for _,worker := range(workers) {
        <-worker.done
        <-worker.done
    }
}

运行代码,我们会发现并发打印完小写字母后出现了死锁的报错:

Worker 4 received e
Worker 0 received a
Worker 3 received d
Worker 1 received b
Worker 2 received c
fatal error: all goroutines are asleep - deadlock!

比较容易的解决方法是改造doWorker,将向外输出done放在一个goroutine中:

func doWork(id int,c chan int,done chan bool) {
    for n:= range(c){
        fmt.Printf("Worker %d received %c \n",id, n)
        go func () { done <- true } ()
    }
}

其实对于等待多任务,golang本身提供了系统库级别的支持sync.waitGroup

type worker struct{
    in chan int
    wg *sync.WaitGroup
}

func doWork(id int,c chan int,wg *sync.WaitGroup) {
    for n:= range(c){
        fmt.Printf("Worker %d received %c \n",id, n)
        wg.Done()
    }
}

func createWorker(id int,wg *sync.WaitGroup) worker {
    w := worker{
        in: make(chan int),
        wg:wg,
    }
    go doWork(id,w.in,wg)
    return w
}

func main()  {
    var workers [5]worker
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        workers[i] = createWorker(i,&wg)
    }
    wg.Add(10)
    for i,worker := range(workers) {
        worker.in <- 'a' + i
    }
    for i,worker := range(workers) {
        worker.in <- 'A' + i
    }
    wg.Wait()
}

使用select进行调度

func generator() chan int {
    out:=make(chan int)
    go func ()  {
        i:=0
        for {
            time.Sleep(time.Duration(rand.Intn(1500)) *  time.Millisecond)
            out <- i
            i++
        }
    }()
    return out
}

func main()  {
    var c1,c2 = generator(),generator()
    tm := time.After(5 * time.Second)
    tick := time.Tick(time.Second)
    for {
        select {
            case n := <- c1:
                fmt.Println("receive from c1:",n)
            case n := <- c2:
                fmt.Println("receive from c2:",n)
            case <- time.After(500 * time.Millisecond):
                // 等待数据太慢
                fmt.Println("timeout")
            case <- tick:
                fmt.Println("this will print every seconds")    
            case <- tm:
                // 计时器终止后退出程序
                fmt.Println("bye")
                return
        }
    }
}
receive from c2: 0
receive from c1: 0
receive from c1: 1
receive from c2: 1
this will print every seconds
receive from c2: 2
timeout
receive from c1: 2
receive from c2: 3
this will print every seconds
timeout
receive from c1: 3
this will print every seconds
receive from c1: 4
receive from c1: 5
receive from c2: 4
receive from c2: 5
receive from c2: 6
receive from c1: 6
this will print every seconds
timeout
receive from c2: 7
this will print every seconds
bye