开放的编程资料库

当前位置:我爱分享网 > Go教程 > 正文

如何在Golang中使用goroutine

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教程。

未经允许不得转载:我爱分享网 » 如何在Golang中使用goroutine

感觉很棒!可以赞赏支持我哟~

赞(0) 打赏