Gogoroutine教程展示了如何在Golang中使用goroutine。goroutine是一个轻量级的执行线程。
$ go version go version go1.18.1 linux/amd64
我们使用Go版本1.18。
协程定义
Goroutine是一个轻量级的执行线程。它是一个与其他正在运行的代码同时运行的函数。请注意,并发执行可能并行也可能不并行。在Go中,每个程序至少有一个goroutine:主goroutine。
goroutine以go关键字开始。
按顺序执行函数
下面的例子一个一个地运行一个函数。
package main
import (
"fmt"
)
func main() {
hello("Martin")
hello("Lucia")
hello("Michal")
hello("Jozef")
hello("Peter")
}
func hello(name string) {
fmt.Printf("Hello %s!\n", name)
}
程序按顺序运行hello函数。
$ go run main.go Hello Martin! Hello Lucia! Hello Michal! Hello Jozef! Hello Peter!
输出总是相同的。
并发执行函数
现在我们并发运行hello函数。
package main
import (
"fmt"
)
func main() {
go hello("Martin")
go hello("Lucia")
go hello("Michal")
go hello("Jozef")
go hello("Peter")
fmt.Scanln()
}
func hello(name string) {
fmt.Printf("Hello %s!\n", name)
}
使用go关键字,我们同时运行hello函数。fmt.Scanln函数等待用户输入。如果我们注释掉这个函数,程序会在我们看到goroutines的输出之前完成。
$ go run main.go Hello Lucia! Hello Michal! Hello Martin! Hello Jozef! Hello Peter! $ go run main.go Hello Martin! Hello Peter! Hello Lucia! Hello Michal! Hello Jozef!
我们运行程序两次。请注意,输出是不同的。
去sync.WaitGroup
sync.WaitGroup是一个同步工具,它等待一组goroutine完成。
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
wg.Add(2)
go func() {
count("oranges")
wg.Done()
}()
go func() {
count("apples")
wg.Done()
}()
wg.Wait()
}
func count(thing string) {
for i := 0; i < 4; i++ {
fmt.Printf("counting %s\n", thing)
time.Sleep(time.Millisecond * 500)
}
}
在程序中,我们用sync.WaitGroup同步两个goroutines的执行。
var wg sync.WaitGroup wg.Add(2)
使用Add,我们告诉我们等待的goroutines有多少。
go func() {
count("oranges")
wg.Done()
}()
我们创建一个匿名goroutine。我们用Done告诉Go运行时goroutine已经完成。
wg.Wait()
Wait函数会阻塞,直到所有goroutine完成。
time.Sleep(time.Millisecond * 500)
在演示程序中,time.Sleep通常用于减慢goroutine的执行速度。
$ go run main.go counting apples counting oranges counting apples counting oranges counting oranges counting apples counting apples counting oranges
去异步请求
下一个示例使用goroutines来发出异步请求。
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"regexp"
"sync"
)
func main() {
urls := []string{
"http://webcode.me",
"https://example.com",
"http://httpbin.org",
"https://www.perl.org",
"https://www.php.net",
"https://www.python.org",
"https://code.visualstudio.com",
"https://clojure.org",
}
var wg sync.WaitGroup
for _, u := range urls {
wg.Add(1)
go func(url string) {
defer wg.Done()
content := doReq(url)
title := getTitle(content)
fmt.Println(title)
}(u)
}
wg.Wait()
}
func doReq(url string) (content string) {
resp, err := http.Get(url)
if err != nil {
log.Println(err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println(err)
return
}
return string(body)
}
func getTitle(content string) (title string) {
re := regexp.MustCompile("<title>(.*)</title>")
parts := re.FindStringSubmatch(content)
if len(parts) > 0 {
return parts[1]
} else {
return "no title"
}
}
我们发出多个异步HTTP请求。我们获取每个网页的title标签的内容。每个请求都包装在一个goroutine中。
go func(url string) {
defer wg.Done()
content := doReq(url)
title := getTitle(content)
fmt.Println(title)
}(u)
使用goroutine,我们生成一个GET请求,接收响应,从响应中获取标题并将其打印到终端。
$ go run main.go The Perl Programming Language - www.perl.org Welcome to Python.org Visual Studio Code - Code Editing. Redefined PHP: Hypertext Preprocessor Example Domain httpbin.org Clojure My html page
协程通道
协程通过通道进行通信。它们允许使用通道运算符<-发送和接收值。
c := make(chan string)
使用make函数创建了一个新频道。
c <- v // send v := <-c // receive
通道运算符在goroutine之间发送和接收值。
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan string)
go hello("Martin", c)
for msg := range c {
fmt.Println(msg)
}
}
func hello(name string, c chan string) {
for i := 0; i < 5; i++ {
msg := fmt.Sprintf("Hello %s!", name)
c <- msg
time.Sleep(time.Millisecond * 500)
}
close(c)
}
在程序中,两个goroutines进行通信:main和hello。
c := make(chan string)
频道是用make创建的。
go hello("Martin", c)
一个hellogoroutine是用go创建的。我们将通道作为参数传递。
for msg := range c {
fmt.Println(msg)
}
使用range关键字,我们遍历消息并将它们打印到控制台。
func hello(name string, c chan string) {
for i := 0; i < 5; i++ {
msg := fmt.Sprintf("Hello %s!", name)
c <- msg
time.Sleep(time.Millisecond * 500)
}
close(c)
}
在hello函数中,我们创建了五个消息并通过通道将它们发送到maingoroutine。当goroutine完成时,我们用close关闭通道。
$ go run main.go Hello Martin! Hello Martin! Hello Martin! Hello Martin! Hello Martin!
使用goroutines计算斐波那契值
在下一个示例中,我们使用goroutines计算斐波那契数。
package main
import (
"fmt"
)
func fib(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fib(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
在fibgoroutine中生成一系列斐波那契值。这些值通过通道一个一个地发送到调用者goroutine。
$ go run main.go 0 1 1 2 3 5 8 13 21 34
这是Golang协程的入门教程。我们提供了一些简单的示例来演示goroutine的用法。
列出所有Go教程。
