现在,Go 是一种令人难以置信的高性能语言,它具有许多强大的功能,可以让您构建令人难以置信的快速应用程序。它通过为我们提供这些 goroutines和channels.
使用 goroutine 是一种非常快速的方法,可以将顺序程序转换为并发程序,而无需担心创建线程或线程池之类的事情。但是,与所有并发编程一样,这会带来一些危险,在你跑来跑去go在所有函数调用前面加上关键字之前必须考虑这些危险 。
在本教程中,我们将研究如何 goroutines在基于 Go 的程序中使用 并随后提高程序执行的性能。
目标
在本教程结束时,您应该:
- 深入了解它们是什么goroutines,以及如何使用它们来提高应用程序的性能
- 知道如何创建和使用匿名 goroutine。
- 了解使您的应用程序并发的一些危险。
什么是 Goroutine?
那么首先,什么是 Goroutines?Goroutine 是由 go 运行时管理得令人难以置信的轻量级“线程”。它们使我们能够创建异步并行程序,这些程序可以比以顺序方式编写的任务更快地执行某些任务。
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函数在我们goroutines有机会执行之前没有完成。
使我们的程序异步
如果我们不关心程序打印值 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。
$去运行main.go
Executing my Concurrent anonymous function
结论
因此,在本教程中,我们学习了如何开始在 Go 中开发并发应用程序。我们研究了 Goroutine 是什么,以及我们如何使用它们来加速系统的各个部分并创建高性能应用程序。
希望您发现本教程很有用,如果您这样做了,请在下面的评论部分告诉我