跳转到主要内容

现在,Go 是一种令人难以置信的高性能语言,它具有许多强大的功能,可让您构建令人难以置信的快速应用程序。通过为我们提供这些 goroutine 和通道,它重新定义了构建并发程序的意义。

使用 goroutine 是一种非常快速的方法,可以将顺序程序转换为并发程序,而无需担心创建线程或线程池之类的事情。但是,与所有并发编程一样,这会带来一些危险,在你跑来跑去在所有函数调用前面加上 go 关键字之前必须考虑这些危险。

在本教程中,我们将研究如何在基于 Go 的程序中使用 goroutine,并随后提高程序执行的性能。

目标


在本教程结束时,您应该:

  • 对 goroutine 是什么以及如何使用它们来提高应用程序的性能有深入的了解
  • 知道如何创建和使用匿名 goroutine。
  • 了解使您的应用程序并发的一些危险。


视频教程


如果您愿意,本教程还提供视频格式。如果您想支持我的工作,请考虑喜欢并订阅我的 YouTube 频道。

https://youtu.be/ARHXmR0_MGY

什么是 Goroutine?


那么首先,什么是 Goroutines? Goroutine 是由 go 运行时管理的令人难以置信的轻量级“线程”。它们使我们能够创建异步并行程序,这些程序可以比以顺序方式编写的任务更快地执行某些任务。

Goroutines 比线程小得多,与需要 1Mb 的线程相比,它们通常需要大约 2kB 的堆栈空间来初始化。

Goroutines 通常被多路复用到非常少量的 OS 线程上,这通常意味着并发 go 程序需要更少的资源才能提供与 Java 等语言相同水平的性能。创建一千个 goroutine 通常最多需要一两个操作系统线程,而如果我们要在 java 中做同样的事情,则需要 1,000 个完整线程,每个线程至少占用 1Mb 的堆空间。

通过将成百上千个 goroutine 映射到单个线程上,我们不必担心在应用程序中创建和销毁线程时会影响性能。由于它们的大小和处理它们的有效方式,创建和销毁新的 goroutine 的成本非常低。

一个简单的顺序程序


作为演示的一种方式,我们将创建一个函数,该函数接收一个 int 值并将一个数字打印到控制台 n 次。我们还将添加一个 sleep 函数,它会在打印第二个数字之前等待一秒钟:

main.go

package main


import (
    "fmt"
    "time"
)


// a very simple function that we'll
// make asynchronous later on
func compute(value int) {
    for i := 0; i < value; i++ {
        time.Sleep(time.Second)
        fmt.Println(i)
    }
}

func main() {
    fmt.Println("Goroutine Tutorial")

    // sequential execution of our compute function
    compute(10)
    compute(10)

    // we scan fmt for input and print that to our console
    var input string
    fmt.Scanln(&input)

}

如果您执行上面的代码,您应该会看到它连续两次打印 0 到 9。 这个顺序程序的总执行时间刚刚超过 20 秒。 我们添加 fmt.Scanln() 的原因是我们的 main 函数在我们的 goroutine 有机会执行之前不会完成。

使我们的程序异步


如果我们不关心程序打印值 0 到 n 的顺序,那么我们可以通过使用 goroutine 并使其异步来加速该程序。

main.go

package main


import (
    "fmt"
    "time"
)

// notice we've not changed anything in this function
// when compared to our previous sequential program
func compute(value int) {
    for i := 0; i < value; i++ {
        time.Sleep(time.Second)
        fmt.Println(i)
    }
}

func main() {
    fmt.Println("Goroutine Tutorial")

    // notice how we've added the 'go' keyword
    // in front of both our compute function calls
    go compute(10)
    go compute(10)
}

 

我们唯一需要更改现有顺序 go 程序的就是在我们的计算函数调用之前添加“go”关键字。 在这里,我们基本上创建了两个独立的 goroutine,它们现在将并行执行。

但是,如果你尝试运行这个程序,你会注意到它在没有打印出我们预期的输出的情况下完成了。

为什么是这样?

嗯,这是因为我们的 main 函数在异步函数执行之前完成,因此,任何尚未完成的 goroutine 都会被立即终止。

为了解决这个问题,我们可以添加对 fmt.Scanln() 的调用,以便我们的程序在杀死我们可怜的 goroutine 之前等待键盘输入:

main.go

package main


import (
    "fmt"
    "time"
)

// notice we've not changed anything in this function
// when compared to our previous sequential program
func compute(value int) {
    for i := 0; i < value; i++ {
        time.Sleep(time.Second)
        fmt.Println(i)
    }
}

func main() {
    fmt.Println("Goroutine Tutorial")

    // notice how we've added the 'go' keyword
    // in front of both our compute function calls
    go compute(10)
    go compute(10)

    var input string
    fmt.Scanln(&input)
}

尝试在您的终端中执行此操作,您将看到 0,0,1,1,2,2... 等等,直到 ..9,9 在我们的控制台中打印出来。 如果你计算这个程序的执行时间,那么我们会突然缩短到大约 10 秒。

匿名 Goroutine 函数


在前面的示例中,我们研究了如何使用 go 关键字使命名函数并发。 但是,碰巧我们也可以使用 go 关键字来使我们的匿名函数并发:

main.go

package main

import "fmt"

func main() {
    // we make our anonymous function concurrent using `go`
    go func() {
        fmt.Println("Executing my Concurrent anonymous function")
    }()
    // we have to once again block until our anonymous goroutine
    // has finished or our main() function will complete without
    // printing anything
    fmt.Scanln()
}

当我们执行这个时,我们会看到我们的匿名 goroutine 已经成功执行并调用了 fmt.Println。

$ go run main.go

Executing my Concurrent anonymous function


结论


因此,在本教程中,我们学习了如何开始在 Go 中开发并发应用程序。 我们研究了 Goroutine 是什么,以及我们如何使用它们来加速系统的各个部分并创建高性能应用程序。

希望您发现本教程很有用,如果您这样做了,请在下面的评论部分告诉我!

文章链接