美文网首页
Go教程第十八篇: Goroutine

Go教程第十八篇: Goroutine

作者: 大风过岗 | 来源:发表于2020-05-06 14:00 被阅读0次

    Go教程第十八篇: Goroutine

    本文是《Go系列教程》的第十八篇文章。在上一节中,我们讨论了并发的概念以及并发和并行的区别。在本文中,我们将讨论一下在Go中如何使用Goroutine实现并发。

    什么是Goroutine ?

    Goroutine是可以和其他函数并发的运行的函数。你可以把Goroutine想象成轻量级的线程。相比于线程,创建Goroutine的开销要小得多。因此,通常一个Go应用程序都有成千个
    Goroutine并发运行。

    相比较于线程,Goroutine的优势

    • 和线程相比,Goroutine的开销极小。它们在栈内存中只有几kb的大小,并且这个内存大小还可以根据应用程序的需要进行扩增和收缩。然而对于线程来说,栈需要事先指定,并且大小
      是固定的不变的。

    • Goroutine是多路复用好几个操作系统线程。在一个应用程序中,可能几千个Goroutine,共用一个操作系统线程。如果线程里的某一个Goroutine阻塞住了,正在等待用户的输入,
      那么,就会创建另一个系统线程, 把其他的Goroutine迁移到新的系统线程上。所有这些动作都是在运行时期执行的,我们作为编程人员不需要关心这些复杂的细节,我们只需要利用
      它提供的API编写并发程序即可。

    • Goroutine之间通过channel进行通信。channel被设计用来防止多个Goroutine访问共享内存导致的资源竞争。你可以把Channel想象成管道,Goroutine使用channel进行通信。我们会在下一个章节详细讨论channel的有关内容。

    如何启动一个Goroutine ?

    在调用函数或方法的时候,加上前缀关键字go,这时,你的方法调用就变成了一个可以并发执行的Goroutine了。我们来创建一个Goroutine:

    package main
    
    import (  
        "fmt"
    )
    
    func hello() {  
        fmt.Println("Hello world goroutine")
    }
    func main() {  
        go hello()
        fmt.Println("main function")
    }
    
    

    go hello() 这行代码会启动一个新的Goroutine。此时hello()函数就会和main函数一起并发运行。main函数会在它自己的Goroutine中运行,我们称之为:main Goroutine。
    运行上面的程序,输出结果只有:main function。并没有输出我们的hello函数中的内容啊,这中间到底发生了什么呢?这时,我们就需要理解一下Goroutine的俩个特性,以及为什么
    会发生这种情况:

    • 当启动一个新的Goroutine时,这个goroutine调用会立即返回。它不像函数调用,控制器不会等到Goroutine执行完成之后再返回。控制器会立即返回,而且忽略掉Goroutine的返回值。

    • 为了确保其他Goroutine能运行,main Goroutine应该处于运行状态。如果main Goroutine结束运行,程序就会结束运行,其他的Goroutine也就不会再运行了。

    我想,现在你已经理解了为什么我们的Goroutine没有运行。在调用完go hello()之后,会立即返回,不会等待hello Goroutine执行完成,而会直接打印main function。之后,main Goroutine的优势会结束运行,因为在这行代码之后,已经没有其他需要执行的代码了,因此hello Goroutine就没有得到运行的机会。

    我们来修改一下:

    package main
    
    import (  
        "fmt"
        "time"
    )
    
    func hello() {  
        fmt.Println("Hello world goroutine")
    }
    func main() {  
        go hello()
        time.Sleep(1 * time.Second)
        fmt.Println("main function")
    }
    
    

    time.Sleep(1 * time.Second)在这行代码中,我们调用了Sleep方法,sleep方法会让当前的goroutine睡眠指定的时间。在本例中,main Goroutine就会睡眠一秒。这样,在main Goroutine结束运行之前,我们的go hello(),就有足够的时间运行。程序的输出结果为:Hello world goroutine,1秒钟之后,又会输出:main function。

    我们在main Goroutine中使用sleep方法,让main Goroutine等待其他Goroutine执行完成。这种方式只是为了让我们理解Goroutine的工作原理。在正式的编程中,我们可以使用channel阻塞住main Goroutine直到其他所有的Goroutine执行完成。我们将在下节中讨论channel。

    启动多个Goroutine

    我们再写一个程序,在这个程序中,我们会启动多个Goroutine。

    package main
    
    import (  
        "fmt"
        "time"
    )
    
    func numbers() {  
        for i := 1; i <= 5; i++ {
            time.Sleep(250 * time.Millisecond)
            fmt.Printf("%d ", i)
        }
    }
    func alphabets() {  
        for i := 'a'; i <= 'e'; i++ {
            time.Sleep(400 * time.Millisecond)
            fmt.Printf("%c ", i)
        }
    }
    func main() {  
        go numbers()
        go alphabets()
        time.Sleep(3000 * time.Millisecond)
        fmt.Println("main terminated")
    }
    
    

    在上面的程序中,我们启动了俩个Goroutine。这俩个Goroutine就可以并发运行了。numbers这个Goroutine初始睡眠250毫秒,之后会打印1。之后再次睡眠,打印2,一直循环直到打印出5。相似地,alphabets的Goroutines页号打印a到e,睡眠400毫秒。main Goroutine会初始化numbers和alphabets的Goroutine,同时睡眠3000毫秒,之后结束。
    程序的输出如下:

    1 a 2 3 b 4 c 5 d e main terminated  
    
    

    下面的图就描绘了程序的工作机制:

    程序是如何执行的

    感谢您的阅读,请留下您珍贵的反馈和评论。Have a good Day!

    备注
    本文系翻译之作原文博客地址

    相关文章

      网友评论

          本文标题:Go教程第十八篇: Goroutine

          本文链接:https://www.haomeiwen.com/subject/gffwghtx.html