下载安卓APP箭头
箭头给我发消息

客服QQ:3315713922

IT业界:并发编程,程序员必修课,来看go语言的巧妙实现,极为干练

作者:课课家教育     来源: 今日头条点击数:778发布时间: 2020-04-30 00:18:03

标签: IT业界编程语言互联网

软考,您想通过吗?一次通过才是硬道理

引言

程序员(英文Programmer)是从事程序开发、程序维护的专业人员。一般将程序员分为程序设计人员和程序编码人员,但两者的界限并不非常清楚,特别是在中国。软件从业人员分为初级程序员、中级程序员、高级程序员(现为软件设计师)、系统分析员,系统架构师,测试工程师六大类。

现在还不能掌握并发编程的程序员,面临被计算机技术淘汰的窘境。本文注重介绍go语言的goroutine实现并发的编程。

 IT业界:并发编程,程序员必修课,来看go语言的巧妙实现,极为干练_IT业界_编程语言_互联网_课课家

什么是GoRoutine

Goroutine是与其他函数或方法并发运行的函数或方法,可以将Goroutine视为轻量级线程。与线程相比,创建Goroutine的成本开销微乎其微。因此,GO应用程序通常同时运行数千个Goroutine。

与线程相比,Goroutine的优势

  • 与线程相比,Goroutine非常节约CPU和内存。它们的堆栈大小只有几KB,堆栈可以根据应用程序的需要进行扩展和缩小,而对于线程,必须指定并固定堆栈大小。
  • Goroutine被多路复用到较少数量的OS线程。具有数千个Goroutine的程序中可能只有一个线程。如果线程中任何Goroutine阻塞并等待用户输入,则创建另一个OS线程,并将剩余的Goroutine移到新的OS线程。所有这些都由运行时(runtime)负责,作为程序员从这些错综复杂的细节中抽象出来,并被赋予一个干净的API来处理并发性。
  • Goroutine使用通道(channel)进行通信。通道的设计可以防止在使用Goroutine访问共享内存时出现争用情况。可以将通道视为Goroutines通信所使用的管道。我们将在下一教程中详细讨论频道。

开始使用GoRoutine

在函数或方法调用前面加上关键字go,您将同时运行一个新的Goroutine。

让我们创建一个Goroutine:)

package main

 

 

import (

 

    "fmt"

 

)

 

 

func hello() {

 

    fmt.Println("Hello world goroutine")

 

}

 

func main() {

 

    go hello()

 

    fmt.Println("main function")

 

}

在第11行,go hello()开始一个新的Goroutine,hello()函数将与main()函数并发运行。main函数在它自己的Goroutine中运行,它被称为main Goroutine。

运行这个程序,你会有惊喜哦!

本程序仅输出了文本函数。那我们开始的Goroutine怎么样了?我们需要了解goroutine的两个重要特性,才能理解为什么会发生这种情况。

  • 当启动新的Goroutine时,goroutine调用立即返回。与函数不同,该控件不等待Goroutine完成执行。在Goroutine调用之后,程序立即返回到下一行代码,并忽略来自Goroutine的任何返回值。
  • 主Goroutine应该为任何其他Goroutine运行。如果主Goroutine终止,则该程序将终止,并且不会运行其他Goroutine。

我想现在你可以理解为什么我们的Goroutine没有跑了。

在第11行调用go hello()之后,程序立即返回到下一行代码,而无需等待hello goroutine完成。

然后,主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")

 

}

在上述程序的第13行中,我们调用了time包的Sleep方法,该方法使执行该程序的go例程延时。在这种情况下,主Goroutine会休眠1秒。

现在,go hello()调用在主Goroutine终止之前有足够的时间执行。此程序首先打印Hello world goroutine,等待1秒,然后打印main函数。

这种在主Goroutine中使用睡眠来等待其他Goroutine完成执行的方式,是我们用来理解Goroutine如何工作的一种技巧。通道(channel)可以用来阻塞主Goroutine,直到所有其他Goroutine完成执行。

启动多个Goroutine

让我们再编写一个启动多个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,代码21和22行。这两个Goroutine现在同时运行。

Goroutine numbers 最初休眠250毫秒,然后打印1,然后再次休眠并打印2,相同的周期发生,直到打印5。类似地,Goroutine alphabets 打印从a到e的字母,并且有400毫秒间隔的休眠时间。

主Goroutine启动numbers() ,alphabets(),然后休眠3000毫秒,然后终止。

上述代码运行后输出内容如下:

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

代码不如图片来的直观,我们使用下图描述该程序的工作方式。

 

蓝色的图像的第一部分代表 numbers Goroutine,栗色的第二部分代表 alphabets Goroutine,绿色的第三部分代表 main Goroutine,最后的部分将上述三部分合并在一起,并向我们展示了程序是如何工作的。

每个框顶部的0毫秒、250毫秒等表示时间(以毫秒为单位),输出在每个框的底部表示为1、2、3,依此类推。

蓝色框告诉我们,1在250ms之后打印,2在500ms之后打印,依此类推。最后一个综合框的底部有值1a23b4c5de,它也是程序的输出。

直观的时序图将帮助你更好地理解GoRoutine运行的机制。