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)
一个hello
goroutine是用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
函数中,我们创建了五个消息并通过通道将它们发送到main
goroutine。当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) } }
在fib
goroutine中生成一系列斐波那契值。这些值通过通道一个一个地发送到调用者goroutine。
$ go run main.go 0 1 1 2 3 5 8 13 21 34
这是Golang协程的入门教程。我们提供了一些简单的示例来演示goroutine的用法。
列出所有Go教程。