美文网首页
goroutine剖析

goroutine剖析

作者: wu_sphinx | 来源:发表于2019-08-01 22:36 被阅读0次

    goroutine是在后台运行的轻量级执行线程,同时它也是Go中实现并发的关键。

    goroutine是什么

    我们在理论上理解了goroutine的工作原理,但在代码中,它是什么呢?goroutine只是一个函数或方法,它在后台与其他goroutine并发运行。goroutine特点不是来自于函数或方法,而是取决于我们是如何调用它。

    Go提供了一个特殊的关键字go来创建goroutine。当我们调用带有go前缀的函数或方法时,该函数或方法在goroutine中执行。让我们看一个简单的例子。

    image.png
    https://play.golang.org/p/pIGsToIA2hL

    以上程序中,我们创建了一个打印Hello WorldprintHello函数到控制台。在main函数中,我们像调用普通函数一样调用printHello(),并得到了预期的结果。
    现在让我们通过printHello函数创建goroutine

    image.png
    https://play.golang.org/p/LWXAgDpTcJP

    如前所述,根据goroutine的语法规则,我们在函数调用前加上go关键字,程序执行得很好。结果如下:

    main execution started
    main execution stopped
    

    Hello World没有被打印出来,这有点奇怪。到底发生了什么?

    goroutine总是在后台运行。在这里,当goroutine被执行时,Go不会阻塞程序的执行,这与我们在前面的示例中看到的普通函数调用不同。相反,调度器会立即返回到下一行代码,并且忽略goroutine返回的任何值。但即便如此,为什么我们看不到函数输出呢?

    默认情况下,每个Go独立应用程序创建一个goroutine叫做main goroutine, main函数在其上运行。在上面的例子中,在main goroutine内部的printHello函数是另一个goroutine,我们将其称为printHello goroutine。因此,当我们执行上面的程序时,有两个goroutine同时运行。正如我们在前面的程序中看到的,goroutines是协作调度的。因此,当main goroutine开始执行时,go调度程序不会将控制权交给给printHello goroutine,直到main goroutine执行完。但是不幸的是,当main goroutine执行完毕时,程序立即退出了,调度器没来得及调度到printHello goroutine上。但是从其他经验中我们知道,使用阻塞条件,我们可以手动调度到其它goroutine,也就是说显示告诉调度程序调度其他可用的goroutine。让我们利用time.Sleep()来做这件事。

    image.png
    https://play.golang.org/p/ujQKjpALlRJ

    我们修改了程序,在main goroutine运行到最后一行代码之前,我们使用time.Sleep(10 * time.Millisecond)显示调度到printHello goroutine。在本例中,main goroutine将休眠10毫秒,并且在10毫秒内不会被再次调度。一旦printHello goroutine执行,它就会输出Hello World!,然后回到main goroutine(在10毫秒之后)来执行堆栈指针所在的最后一行代码。因此,上面的程序生成以下结果。

    main execution started
    Hello World!
    main execution stopped
    

    如果我们在函数中添加一个sleep调用,它会告诉goroutine调度另一个可用的goroutine,在本例中是main goroutine。但是从上节中,我们了解到只有非睡眠的goroutine才会被调度,main在它睡眠的10毫秒内不会被调度。因此,main goroutine将打印main execution started,生成printHello goroutine,但仍在积极运行,然后休眠10毫秒,并将控制权传递给printHello goroutine。然后printHello goroutine会休眠1毫秒,告诉调度程序调度另一个goroutine,但由于没有可用的goroutine,在1毫秒后打印Hello World!, 然后就挂了。然后main goroutine将在几毫秒后重新执行,在打印main execution stopped后退出程序。

    image.png
    https://play.golang.org/p/rWvzS8UeqD6

    以上程序仍将打印相同的结果

    main execution started
    Hello World!
    main execution stopped
    

    如果不是1毫秒,而是15毫秒,会发生什么呢?

    image.png
    https://play.golang.org/p/Pc2nP2BtRiP

    在这种情况下,main goroutine将在printHello goroutine醒来之前可用来调度调度程序,这也将在调度程序有时间再次调度printHello goroutine之前立即终止程序。因此,它的产量将低于计划

    main execution started
    main execution stopped
    

    多个goroutines协同

    正如我前面所说,您可以创建尽可能多的goroutine。让我们定义两个简单的函数,一个打印字符串的字符,另一个打印整数片的数字。

    image.png
    https://play.golang.org/p/SJano_g1wTV

    在上面的程序中,我们从两个函数调用中创建了两个goroutine。然后我们调度两个goroutine中的任何一个,调度哪个goroutine由调度程序决定。这将产生以下结果

    main execution started
    H e l l o 1 2 3 4 5 
    main execution stopped
    

    上述结果再次证明了goroutine是协同调度的。我们再加一次。函数定义中的打印操作之间的休眠调用,以告诉调度程序调度其他可用的goroutine

    在上面的程序中,我们打印了额外的信息,以查看从程序执行时起打印语句何时执行。理论上,main goroutine将休眠200毫秒,因此所有其他goroutine必须在200毫秒内完成它们的工作,然后才会唤醒并终止程序。getChars goroutine将打印一个字符并休眠10毫秒,调度getDigits goroutine, getDigits goroutine将打印一个数字,休眠3毫秒,当getChars goroutine醒来时,再次调度getChars goroutine。因为getChars goroutine可以多次打印和休眠,至少在其他goroutine休眠时可以打印2次,所以我们希望看到连续打印的字符比数字多。

    下面的结果是从运行上述程序在Windows机器。

    main execution started at time 0s
    H at time 1.0012ms                         <-|
    1 at time 1.0012ms                           | almost at the same time
    e at time 11.0283ms                        <-|
    l at time 21.0289ms                          | ~10ms apart 
    l at time 31.0416ms
    2 at time 31.0416ms
    o at time 42.0336ms
    3 at time 61.0461ms                        <-|
    4 at time 91.0647ms                          | 
    5 at time 121.0888ms                         | ~30ms apart
    main execution stopped at time 200.3137ms    | exiting after 200ms
    

    我们可以看到我们讨论过的模式。一旦您看到程序执行关系图,这将被清除。我们将近似于打印命令需要1ms的CPU时间,与200ms相比,这是微不足道的。


    image.png

    现在我们了解了如何创建goroutine以及如何使用它们。但使用时间。睡觉只是为了看看结果。在生产中,我们不知道goroutine执行需要多少时间。因此,我们不能只是在主函数中添加随机睡眠调用。我们想让goroutines告诉我们他们什么时候执行完毕。同样,在这一点上,我们不知道如何从其他goroutine获取数据或将数据传递给它们,简单地说,就是与它们通信。这就是频道的作用。让我们在下节课讨论它们。

    匿名goroutines

    如果匿名函数可以存在,那么匿名goroutine也可以退出。请立即从函数课中阅读已调用函数以理解本节。让我们修改前面的printHello goroutine示例。

    image.png
    https://play.golang.org/p/KSzsPIuG-Ph

    结果非常明显,因为我们在同一语句中定义了函数并将其作为goroutine执行。
    所有goroutine都是匿名的,因为goroutine没有标识。但我们调用它的意义是,创建它的函数是匿名的。

    所有goroutine都是匿名的,因为goroutine没有标识。但我们调用它的意义是,创建它的函数是匿名的。

    翻译原文

    相关文章

      网友评论

          本文标题:goroutine剖析

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