通过三个例子,学习 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 这两门编程语言,在实现同一需求所需的不同代码量的差异,更是深有体会。

相关文章
|
5天前
|
数据采集 存储 Go
使用Go语言和chromedp库下载Instagram图片:简易指南
Go语言爬虫示例使用chromedp库下载Instagram图片,关键步骤包括设置代理IP、创建带代理的浏览器上下文及执行任务,如导航至用户页面、截图并存储图片。代码中新增`analyzeAndStoreImage`函数对图片进行分析和分类后存储。注意Instagram的反爬策略可能需要代码适时调整。
使用Go语言和chromedp库下载Instagram图片:简易指南
|
1天前
|
Go 开发者
Golang深入浅出之-Go语言上下文(context)包:处理取消与超时
【4月更文挑战第23天】Go语言的`context`包提供`Context`接口用于处理任务取消、超时和截止日期。通过传递`Context`对象,开发者能轻松实现复杂控制流。本文解析`context`包特性,讨论常见问题和解决方案,并给出代码示例。关键点包括:1) 确保将`Context`传递给所有相关任务;2) 根据需求选择适当的`Context`创建函数;3) 定期检查`Done()`通道以响应取消请求。正确使用`context`包能提升Go程序的控制流管理效率。
6 1
|
1天前
|
程序员 Go
Golang深入浅出之-Select语句在Go并发编程中的应用
【4月更文挑战第23天】Go语言中的`select`语句是并发编程的关键,用于协调多个通道的读写。它会阻塞直到某个通道操作可行,执行对应的代码块。常见问题包括忘记初始化通道、死锁和忽视`default`分支。要解决这些问题,需确保通道初始化、避免死锁并添加`default`分支以处理无数据可用的情况。理解并妥善处理这些问题能帮助编写更高效、健壮的并发程序。结合使用`context.Context`和定时器等工具,可提升`select`的灵活性和可控性。
10 2
|
2天前
|
安全 Go 开发者
Golang深入浅出之-Go语言并发编程面试:Goroutine简介与创建
【4月更文挑战第22天】Go语言的Goroutine是其并发模型的核心,是一种轻量级线程,能低成本创建和销毁,支持并发和并行执行。创建Goroutine使用`go`关键字,如`go sayHello(&quot;Alice&quot;)`。常见问题包括忘记使用`go`关键字、不正确处理通道同步和关闭、以及Goroutine泄漏。解决方法包括确保使用`go`启动函数、在发送完数据后关闭通道、设置Goroutine退出条件。理解并掌握这些能帮助开发者编写高效、安全的并发程序。
10 1
|
2天前
|
人工智能 Go 调度
掌握Go并发:Go语言并发编程深度解析
掌握Go并发:Go语言并发编程深度解析
|
2天前
|
SQL 关系型数据库 MySQL
Golang数据库编程详解 | 深入浅出Go语言原生数据库编程
Golang数据库编程详解 | 深入浅出Go语言原生数据库编程
|
3天前
|
Go 开发者
Golang深入浅出之-Go语言流程控制:if、switch、for循环详解
【4月更文挑战第21天】本文介绍了Go语言中的流程控制语句,包括`if`、`switch`和`for`循环。`if`语句支持简洁的语法和初始化语句,但需注意比较运算符的使用。`switch`语句提供多分支匹配,可省略`break`,同时支持不带表达式的形式。`for`循环有多种形式,如基本循环和`for-range`遍历,遍历时修改原集合可能导致未定义行为。理解并避免易错点能提高代码质量和稳定性。通过实践代码示例,可以更好地掌握Go语言的流程控制。
11 3
Golang深入浅出之-Go语言流程控制:if、switch、for循环详解
|
3天前
|
Go
Golang深入浅出之-Go语言函数基础:定义、调用与多返回值
【4月更文挑战第21天】Go语言函数是代码组织的基本单元,用于封装可重用逻辑。本文介绍了函数定义(包括基本形式、命名、参数列表和多返回值)、调用以及匿名函数与闭包。在函数定义时,注意参数命名和注释,避免参数顺序混淆。在调用时,要检查并处理多返回值中的错误。理解闭包原理,小心处理外部变量引用,以提升代码质量和可维护性。通过实践和示例,能更好地掌握Go语言函数。
17 1
Golang深入浅出之-Go语言函数基础:定义、调用与多返回值
|
4天前
|
程序员 Go API
【Go语言快速上手(二)】 分支与循环&函数讲解
【Go语言快速上手(二)】 分支与循环&函数讲解
|
4天前
|
Go
Golang深入浅出之-Go语言基础语法:变量声明与赋值
【4月更文挑战第20天】本文介绍了Go语言中变量声明与赋值的基础知识,包括使用`var`关键字和简短声明`:=`的方式,以及多变量声明与赋值。强调了变量作用域、遮蔽、初始化与零值的重要性,并提醒读者注意类型推断时的一致性。了解这些概念有助于避免常见错误,提高编程技能和面试表现。
19 0