Bootstrap

15分钟学 Go 第 23 天:并发基础:Goroutines

第23天:并发基础:Goroutines

欢迎来到Go语言学习的第23天!今天我们将深入探讨Goroutines的概念,这是Go语言并发编程的核心要素。理解Goroutines的工作原理和使用方法,将使你能够编写高效的并发应用程序。

1. 并发和并行的概念

在深入Goroutines之前,我们需要清楚理解并发并行的区别:

  • 并发(Concurrency)是指多个任务在同一时间段内进行调度,但并不一定同时执行。
  • 并行(Parallelism)则是指多个任务在同一时刻真正同时执行。

Go语言的并发机制主要通过Goroutines实现。

1.1 Goroutines与传统线程的对比

特性Goroutines传统线程
创建开销轻量级,创建简单开销较大,创建复杂
管理由Go运行时(Go runtime)管理需要操作系统调度
内存占用每个Goroutine占用约2KB每个线程占用更多内存
数量可以同时运行数万个Goroutines受限于操作系统,通常为数百个

2. 什么是Goroutines

Goroutines是Go语言提供的轻量级线程,用于并发执行。Goroutines的创建和调度非常高效,因此开发人员可以创建成千上万的Goroutines。

2.1 创建Goroutine

在Go中,通过使用 go 关键字来创建一个Goroutine。例如:

go func() {
    fmt.Println("Hello from Goroutine")
}()

上面的代码创建了一个新的Goroutine,运行一个匿名函数。

3. 设置Goroutines示例

接下来,我们来看一个使用Goroutines的简单示例,其中我们将启动多个Goroutines来并发执行任务。

3.1 示例代码

package main

import (
    "fmt"
    "time"
)

func printNumbers(from, to int) {
    for i := from; i <= to; i++ {
        fmt.Println(i)
        time.Sleep(100 * time.Millisecond) // 模拟耗时操作
    }
}

func main() {
    fmt.Println("Starting Goroutines")
    
    go printNumbers(1, 5) // 启动第一条Goroutine
    go printNumbers(6, 10) // 启动第二条Goroutine

    // 主Goroutine等待输入,以保持程序运行
    var input string
    fmt.Scanln(&input)
}

3.2 代码说明

  • printNumbers 函数:打印从 fromto 的数字,并在每次打印后暂停100毫秒,模拟一些耗时操作。
  • main 函数
    • 使用 go 关键字启动两个Goroutines,分别打印1到5和6到10的数字。
    • 主Goroutine使用 fmt.Scanln 保持运行,直到有用户输入,以便等待Goroutines完成。

3.3 代码运行流程图

下面是代码执行流程的示意图:

+---------------------+
|      main()        | 
+---------------------+
| Start Goroutine 1  |
| start printNumbers(1,5) |
+---------------------+
| Start Goroutine 2  |
| start printNumbers(6,10) |
+---------------------+
| Wait for user input |
+---------------------+

       |
       v
+---------------------+
|  printNumbers(1,5)  |
|                      |
+---------------------+
|  printNumbers(6,10) |
|                      |
+---------------------+

3.4 运行示例

在项目目录下运行该程序:

go run main.go

你应该能够看到1到10的数字交替打印,表示两个Goroutines正并发执行。

4. Goroutines的调度

Goroutines的调度是由Go的运行时系统自动管理的。它会在适当的时候将Goroutines从一个操作系统线程切换到另一个线程,确保尽可能高效地利用CPU资源。

4.1 Go调度器

Go调度器基于M:N调度模型,其中M是操作系统线程的数量,N是Goroutines的数量。Go会根据需要在有限的线程资源中调度多个Goroutines。

5. Goroutines的特性

5.1 Goroutines是非阻塞的

Goroutines是非阻塞的,即当一个Goroutine在执行某个操作时,其他Goroutines仍然可以继续执行。

5.2 Goroutines的生命周期

  • Goroutines开始时由 go 关键字触发。
  • 当Goroutine退出时(执行完毕或发生错误),其资源会被自动释放。

6. 管理Goroutines

在有些情况下,我们需要控制Goroutines的数量,或等待它们完成。常用的方法有使用WaitGroup与Channel。

6.1 使用WaitGroup

示例代码

package main

import (
    "fmt"
    "sync"
    "time"
)

func printNumbers(from, to int, wg *sync.WaitGroup) {
    defer wg.Done() // 在函数结束时调用Done
    for i := from; i <= to; i++ {
        fmt.Println(i)
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    var wg sync.WaitGroup
    
    fmt.Println("Starting Goroutines")

    wg.Add(2) // 添加2个Goroutine
    go printNumbers(1, 5, &wg)
    go printNumbers(6, 10, &wg)
    
    wg.Wait() // 等待所有Goroutines完成
    fmt.Println("All Goroutines completed")
}

6.2 代码说明

  • 使用 sync.WaitGroup 来管理多个Goroutines的执行。
  • 在每个Goroutine中,调用 wg.Done() 表示完成该Goroutine的任务。
  • 在主Goroutine中,调用 wg.Wait() 来等待所有的Goroutines完成。

6.3 运行输出

你可以运行这个示例并观察输出,程序将在所有Goroutines完成后打印“所有Goroutines完成”。

7. 使用Channel与Goroutines

Channel是Go语言中用于在Goroutines之间通信的同步原语。通过Channel,你可以在多个Goroutines之间安全地传递数据。

7.1 基本用法

示例代码

package main

import (
    "fmt"
    "time"
)

func printNumbers(ch chan int) {
    for i := 1; i <= 5; i++ {
        time.Sleep(100 * time.Millisecond)
        ch <- i // 发送数据到Channel
    }
    close(ch) // 关闭Channel
}

func main() {
    ch := make(chan int) // 创建Channel

    go printNumbers(ch) // 启动Goroutine

    // 从Channel读取数据
    for num := range ch {
        fmt.Println(num)
    }
    
    fmt.Println("All numbers printed")
}

7.2 代码说明

  • printNumbers 函数向 Channel 中发送1到5之间的数字,并最终关闭Channel。
  • 主Goroutine从Channel中读取数据,直到Channel关闭。

7.3 代码运行流程图

+---------------------+
|      main()        | 
+---------------------+
| Create Channel      |
+---------------------+
| Start Goroutine     |
| printNumbers(ch)    |
+---------------------+
       |
       v
+---------------------+
|  printNumbers       |
|                     |
|  Send numbers to    |
|  Channel            |
+---------------------+
       |
       v
+---------------------+
|   Read from Channel  |
|   Print numbers      |
+---------------------+

8. 总结与练习

在今天的学习中,我们深入理解了Goroutines的概念及其重要性。Goroutines允许我们轻松编写并发程序,而无需涉及复杂的线程管理。

练习

  1. 创建一个程序,启动多个Goroutines,并以随机顺序打印字母A到Z。
  2. 使用Channel实现一个Goroutine生产者-消费者模型,生产者生成数字,消费者打印数字。

怎么样今天的内容还满意吗?再次感谢观众老爷的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!

;