1. func main() {
    2. for i := 0; i < 10; i++ {
    3. // 开一个Goroutine(相当于开一个协程)并发地执行函数,同时主程序也会继续执行
    4. // (主程序也是一个Goroutine)
    5. go func(i int) {
    6. for {
    7. // 进行I/O操作时会交出控制权
    8. fmt.Printf("Hello from "+"goroutine %d\n", i)
    9. }
    10. }(i)
    11. }
    12. // 一旦main函数退出,所有的Goroutine就会被杀死
    13. time.Sleep(time.Millisecond)
    14. }


    Goroutine是一种协程(Coroutine)。
    协程是“轻量级”的线程。之所以说是“轻量级”是因为
    协程属于非抢占式多任务处理,即由协程主动决定是否交出控制权;
    而线程属于抢占式多任务处理,即由操作系统来决定控制权给谁。
    线程是操作系统层面的多任务,而协程是编译器/解释器/虚拟机层面的多任务。

    Goroutine是编译器级别的多任务,由Go语言调度器来调度。
    多个Goroutine可能在一个或多个线程上运行,由Go语言调度器决定。

    定义Goroutine:
    任何函数只需加上go关键字就能成为协程并送给调度器运行

    func main() {
        var a [10]int
        for i := 0; i < 10; i++ {
            // 开一个Goroutine(相当于开一个协程)并发地执行函数,同时主程序也会继续执行
            // (主程序也是一个Goroutine)
            go func(i int) {
                for {
                    a[i]++
                    runtime.Gosched() // 手动交出控制权
                }
            }(i)
        }
        time.Sleep(time.Millisecond)
        fmt.Println(a) 
        // [1709 809 1563 1564 1627 1628 1549 1584 833 1231]
    }
    

    子程序(函数调用)是协程的一个特例:
    image.png
    左图为子程序(函数调用),main函数调用doWork函数,把控制权交给doWork函数。doWork函数执行完毕后返回main函数,控制权又回到main函数手里。main函数和doWork函数之间数据和控制权只能单向传递。
    右图为协程,main函数和其过程中所开的协程doWork之间有一条双向的通道,实现数据、控制权双向流通。
    main和doWork这两个协程可能运行在一个线程里,也可能不在一个线程里。

    Go语言的协程(Goroutine):
    image.png
    上图为一个Go语言进程,其中包含一个调度器,调度器负责调度协程。
    一个线程中可能有一个协程,也可能有多个协程。

    Goroutine和传统意义上的协程有一点区别:
    协程属于非抢占式多任务处理,除非协程主动交出控制权,否则不能抢占其控制权;
    Goroutine大体上也是非抢占式的,但是调度器会在合适的点对控制权进行切换,切换的点我们并不能完全控制。

    Goroutine可能的控制权切换点(仅作参考,不能保证在这些点一定切换,也不能保证在其他地方不切换):
    ①I/O,select
    ②channel
    ③等待锁
    ④函数调用(有时)
    ⑤runtime.Gosched()