Go语言编程入门超级指南

2020-01-28 12:01:04王冬梅

4.1 goroutine

goroutine使用go关键字来调用函数,也可以使用匿名函数。可以简单的把go关键字调用的函数想像成pthread_create。如果一个goroutine没有被阻塞,那么别的goroutine就不会得到执行。也就是说goroutine阻塞时,Golang会切换到其他goroutine执行,这是非常好的特性!Java对类似goroutine这种的协程没有原生支持,像Akka最害怕的就是阻塞。因为协程不等同于线程,操作系统不会帮我们完成“现场”保存和恢复,所以要实现goroutine这种特性,就要模拟操作系统的行为,保存方法或函数在协程“上下文切换”时的Context,当阻塞结束时才能正确地切换回来。像Kilim等协程库利用字节码生成,能够胜任,而Akka完全是运行时的。

注意:如果你要真正的并发,需要调用runtime.GOMAXPROCS(CPU_NUM)设置。

package main

import "fmt"

func main() {
    go f("goroutine")

    go func(msg string) {
        fmt.Println(msg)
    }("going")

    // Block main thread
    var input string
    fmt.Scanln(&input)
    fmt.Println("done")
}

func f(msg string) {
    fmt.Println(msg)
}

4.2 原子操作

像Java一样,Golang支持很多CAS操作。运行结果是unsaftCnt可能小于200,因为unsafeCnt++在机器指令层面上不是一条指令,而可能是从内存加载数据到寄存器、执行自增运算、保存寄存器中计算结果到内存这三部分,所以不进行保护的话有些更新是会丢失的。

package main

import (
    "fmt"
    "time"
    "sync/atomic"
    "runtime"
)

func main() {
    // IMPORTANT!!!
    runtime.GOMAXPROCS(4)

    // thread-unsafe
    var unsafeCnt int32 = 0
    for i := 0; i < 10; i++ {
        go func() {
            for i := 0; i < 20; i++ {
                time.Sleep(time.Millisecond)
                unsafeCnt++
            }
        }()
    }
    time.Sleep(time.Second)
    fmt.Println("cnt: ", unsafeCnt)

    // CAS toolkit
    var cnt int32 = 0
    for i := 0; i < 10; i++ {