通过三个例子,学习 Go 语言并发编程的利器 - goroutine

简介: 通过三个例子,学习 Go 语言并发编程的利器 - goroutine

Go 语言(也称为 Golang)是一门由 Google 开发的编程语言,以其简洁、高效和对并发编程的内置支持而在编程领域享有盛名。


在 Go 语言中,goroutine 是一项强大的并发特性,用于轻量级线程的创建和管理。


本文将向没有接触过 Go 语言的朋友,介绍 goroutine 的概念、使用场合,并提供具体的例子以演示其用法。

1. Goroutine 的概念:

Goroutine 是 Go 语言中用于并发执行的轻量级线程。与传统的线程相比,goroutine 的创建和销毁成本很低,可以在同一个程序中轻松创建多个 goroutine 而不会导致性能下降。Goroutines 之间通过通道(channel)进行通信,实现了并发编程的简洁和安全。


2. Goroutine的使用场合


俗话说,红粉赠佳人,宝剑送烈士。既然 Goroutine 有如此突出的能力,并发编程领域就是它大显身手的舞台。


在下列这些应用场景里,我们都能看到 Goroutine 施展拳脚的身影。


  • 并发执行任务: Goroutine 可用于同时执行多个任务,提高程序的性能。
  • 非阻塞 I/O 操作: 在进行 I/O 操作时,可以使用 goroutine 确保其他任务继续执行,而不是同步等待 I/O 完成。
  • 事件驱动编程: Goroutine 可用于处理事件,如监听 HTTP 请求、处理用户输入等。
  • 并发算法: 实现一些需要并行计算的算法,通过 goroutine 可以更轻松地管理并发执行的部分。
  • 定时任务: 使用 goroutine 和定时器可以实现定时执行的任务。

3. Goroutine 的具体例子:

我们通过一些具体的例子来说明。

例子1:简单的并发任务执行

作为热身,我们通过一些简单的 Hello World 级别的例子来熟悉 Goroutine 的用法。

源代码如下:

package main
import (
  "fmt"
  "sync"
)
func printNumbers(wg *sync.WaitGroup) {
  defer wg.Done()
  for i := 1; i <= 5; i++ {
    fmt.Printf("%d ", i)
  }
}
func printLetters(wg *sync.WaitGroup) {
  defer wg.Done()
  for char := 'a'; char <= 'e'; char++ {
    fmt.Printf("%c ", char)
  }
}
func main() {
  var wg sync.WaitGroup
  wg.Add(2)
  go printNumbers(&wg)
  go printLetters(&wg)
  wg.Wait()
}

将上述源代码另存为一个 .go 文件里,比如取名 1.go, 然后执行命令行 go run 1.go, 看到下面的输出:

在这个例子中,我们通过关键字 go,创建了两个 goroutine,一个用于打印数字,另一个用于打印字母。sync.WaitGroup 函数调用,用于等待这两个 goroutine 执行完毕。这是 go 语言并发编程里,最常用的同步机制之一。

例子2:并发下载多个网页

源代码如下:

package main
import (
  "fmt"
  "io/ioutil"
  "net/http"
  "sync"
)
func fetch(url string, wg *sync.WaitGroup) {
  defer wg.Done()
  response, err := http.Get(url)
  if err != nil {
    fmt.Printf("Error fetching %s: %v\n", url, err)
    return
  }
  defer response.Body.Close()
  body, err := ioutil.ReadAll(response.Body)
  if err != nil {
    fmt.Printf("Error reading response body from %s: %v\n", url, err)
    return
  }
  fmt.Printf("Length of %s: %d\n", url, len(body))
}
func main() {
  var wg sync.WaitGroup
  urls := []string{"https://www.baidu.com", "https://cloud.tencent.com/", "https://www.qq.com/"}
  for _, url := range urls {
    wg.Add(1)
    go fetch(url, &wg)
  }
  wg.Wait()
}

执行上面的源代码后,在控制台看到下列输出,打印了三个网站(百度,qq 和腾讯云社区)首页 html 文件的长度(length)。

这个例子展示了如何使用 goroutine 并发地下载多个网页。每个网页都在单独的goroutine中进行下载,从而减少了完成下载任务所需花费的时间。


从源代码可以看出,如果是用 Java 这门传统的编程语言完成这个需求的编码工作,我们需要使用 Java 提供的 Thread 类和一些繁琐的同步机制代码。而使用 Go 语言,通过 go fetch(url, &wg) 这一行短小精悍的语言,就可以轻松完成 goroutine 的启动和根据 url 读取 HTML 页面数据的操作,代码大大简化。

例子3:通过通道实现生产者-消费者模型

生产者-消费者是操作系统课程里必学的概念之一。我们使用 func 关键字,分别定义了生产者和消费者两个函数。生产者函数,使用 time.Sleep 方法,每隔 0.5 秒钟生产一个正整数序列 1,2,3,4,5. 消费者函数,相应的每隔 0.5 秒钟消费一个正整数。

package main
import (
  "fmt"
  "sync"
  "time"
)
func producer(ch chan<- int, wg *sync.WaitGroup) {
  defer wg.Done()
  for i := 1; i <= 5; i++ {
    ch <- i
fmt.Printf("Produced: %d\n", i)
    time.Sleep(time.Millisecond * 500) // 模拟生产过程的延迟
  }
  close(ch)
}
func consumer(ch <-chan int, wg *sync.WaitGroup) {
  defer wg.Done()
  for num := range ch {
    fmt.Printf("Consumed: %d\n", num)
    time.Sleep(time.Millisecond * 200) // 模拟消费过程的延迟
  }
}
func main() {
  var wg sync.WaitGroup
  ch := make(chan int, 3) // 创建带缓冲通道,容量为3
  wg.Add(2)
  go producer(ch, &wg)
  go consumer(ch, &wg)
  wg.Wait()
}

执行上面的 go 源代码,输出如下,生产一个正整数序列,然后消费,以此类推。

这个例子演示了如何使用 goroutine 和通道实现生产者-消费者模型。生产者将数据发送到通道,而消费者从通道中接收并处理数据,通过 goroutine 实现了并发的生产和消费过程。


我们使用了带缓冲的通道,容量为3。带缓冲的通道允许在通道未被完全读取的情况下进行写入,这在生产者和消费者速度不一致时非常有用。


在生产者函数 producer 里,在ch 是一个只写的整数通道,wg 是一个 sync.WaitGroup 类型的指针,用于等待所有的goroutine完成。在这个函数中,生产者通过 ch <- i 向通道发送整数 i,表示生产了一个产品。然后,通过 fmt.Printf 打印出生产的产品的信息,并通过 time.Sleep 模拟生产过程的延迟。最后,通过 close(ch) 关闭通道,表示生产者不再向通道发送数据。


再看消费者函数 consumer. ch 是一个只读的整数通道,wg 是一个 sync.WaitGroup 类型的指针。在这个函数中,消费者通过 num := <-ch 从通道接收整数,表示消费了一个产品。然后,通过 fmt.Printf 打印出消费的产品的信息,并通过 time.Sleep 模拟消费过程的延迟。这个循环会一直执行,直到通道被关闭,此时 range ch 将会退出。


总结

通过本文介绍的这三个例子,我们可以看到 goroutine 的强大之处。通过goroutine 和 channel 的组合,以及 sync.WaitGroup 的使用,Go 语言提供了强大而简洁的并发编程机制。相信之前有过 Java 编程经验的开发者,对于 Java 和 Go 这两门编程语言,在实现同一需求所需的不同代码量的差异,更是深有体会。

相关文章
|
3天前
|
API Go
使用Go语言通过API获取代理IP并使用获取到的代理IP
使用Go语言通过API获取代理IP并使用获取到的代理IP
|
4天前
|
前端开发 Java Go
开发语言详解(python、java、Go(Golong)。。。。)
开发语言详解(python、java、Go(Golong)。。。。)
|
13天前
|
存储 Java 编译器
go语言基础语法
go语言基础语法
|
13天前
|
Go
go语言中的数据类型
go语言中的数据类型
11 0
|
19天前
|
存储 安全 Go
掌握Go语言:Go语言类型转换,无缝处理数据类型、接口和自定义类型的转换细节解析(29)
掌握Go语言:Go语言类型转换,无缝处理数据类型、接口和自定义类型的转换细节解析(29)
|
19天前
|
存储 缓存 安全
掌握Go语言:Go语言Map,高效键值对集合的应用与注意事项详解(26)
掌握Go语言:Go语言Map,高效键值对集合的应用与注意事项详解(26)
|
19天前
|
存储 安全 编译器
掌握Go语言:精通Go语言范围(range),高级应用及进销存系统实战(25)
掌握Go语言:精通Go语言范围(range),高级应用及进销存系统实战(25)
|
19天前
|
Go 开发者
掌握Go语言:Go语言结构体,精准封装数据,高效管理实体对象(22)
掌握Go语言:Go语言结构体,精准封装数据,高效管理实体对象(22)
|
19天前
|
安全 Go
掌握Go语言:Go语言通道,并发编程的利器与应用实例(20)
掌握Go语言:Go语言通道,并发编程的利器与应用实例(20)
|
19天前
|
存储 缓存 安全
掌握Go语言:Go语言中的字典魔法,高效数据检索与应用实例解析(18)
掌握Go语言:Go语言中的字典魔法,高效数据检索与应用实例解析(18)

热门文章

最新文章