likes
comments
collection
share

5分钟了解 Go 语言中的 Goroutine 并发模型

作者站长头像
站长
· 阅读数 21

一、前言

在 Go 中,有一个核心的概念就是 Goroutine,它是并发编程的基础。本文将探讨 Goroutine,以帮助你更好地理解并发编程在 Go 语言中的运作。

二、内容

2.1 什么是 Goroutine

“Go 的主要特色在于易于使用的并行设计,叫做 Goroutine。透过 Goroutine 能够让程序以异步的方式执行,而不需要担心一个函数导致程序中断,因此 Go 也非常地适合网络服务。” —— 维基百科

Goroutine 是 Go 语言中的并发执行单元,与传统操作系统线程相比,它们非常轻量级。创建和销毁 Goroutine 的成本很低,这意味着你可以轻松地创建大量的 Goroutine,而不会造成显著的系统开销。这种轻量级特性使得 Go 能够高效地处理大规模并发任务,而不会导致系统负载急剧增加。

注意, Goroutine 是由 Go 语言的运行时系统进行管理和调度的。程序员不需要手动管理线程的创建和销毁,也不必担心线程之间的调度问题。这种自动管理的特性减轻了开发者的负担,让他们更专注于编写应用程序的逻辑。通过创建多个 Goroutine,我们可以同时执行多个任务,这对于处理网络请求、数据处理、并行计算等场景非常有用。Goroutine 的并发性让编写高性能、高并发的应用变得更加容易。

2.2 异步执行

Goroutine 的真正魅力在于其能够以异步的方式执行代码块,而无需担心一个函数的执行会阻塞整个程序。这意味着我们可以将耗时的操作,例如文件读写、网络请求或计算密集型任务,委托给 Goroutine 来处理,而主程序可以继续执行其他任务,而不会被阻塞。这种异步执行的方式使得 Go 语言非常适合构建高响应性的网络服务和并发应用程序。

2.3 Goroutine 的创建

在Go语言中,创建一个 Goroutine 非常简单,只需在函数调用前加上关键字 go 即可。

例如:

go func() {
    // 这里是要在Goroutine中执行的代码
}()

上述代码片段就创建了一个新的 Goroutine ,它会异步执行匿名函数中的代码。这种方式极其高效,因为 Goroutine 的创建和销毁开销很小,且不需要像传统线程那样复杂的线程管理。

Go运行时包含一个高效的 Goroutine 调度器,它负责在不同的 Goroutine 之间进行任务切换,以实现并发执行。这个调度器在后台自动管理 Goroutine 的调度,使得开发者无需手动干预,减轻了多线程编程的复杂性。

Go运行时还提供了垃圾回收和内存管理,确保了 Goroutine 的资源有效利用和释放,避免了内存泄漏等问题。

当一个程序启动时,其主函数即在一个单独的goroutine中运行,我们叫它main goroutine

2.4 简单的例子

下面我们举一个简单的例子,用于展示如何使用 goroutine 来实现简单的并发。

需要注意的是,main goroutine 将计算菲波那契数列第 45 个元素值。这是一个计算密集型任务,因此需要耗费一点时间,我们在这个期间并发运行 animateProgressBar 函数,用于在终端显示一个进度条,以模拟一个正在运行的任务的进度。

package main

import (
    "fmt"
    "time"
)

func main() {
    // 打印进度条
    go animateProgressBar(40 * time.Millisecond)

    // 计算菲波那契数列第 45 个元素值
    result := fib(45)

    fmt.Printf("\nresult = %d.\nMain goroutine exiting.", result)
}

// animateProgressBar 函数用于显示进度条
func animateProgressBar(delay time.Duration) {
    const width = 50
    progress := 0
    for {
        fmt.Printf("\r[%s] %d%%", progressBar(progress, width), progress)
        time.Sleep(delay)
        if progress == 100 {
            return
        }
        progress = (progress + 1) % 101
	}
}

// progressBar 生成进度条字符串
func progressBar(progress, width int) string {
    numBars := (progress * width) / 100
    bars := make([]byte, width)
    for i := 0; i < numBars; i++ {
        bars[i] = '='
    }
    return string(bars)
}

// fib 计算斐波那契数列的第 x 个元素
func fib(x int) int {
    if x < 2 {
        return x
    }
    return fib(x-1) + fib(x-2)
}

运行结果如下:

[==================================================] 100%
result = 1134903170.
Main goroutine exiting.

在这个示例中,两个独立的任务(显示进度条和计算斐波那契数列)是通过goroutine并发执行的。虽然它们在不同的函数中,但它们可以同时运行,而不会相互干扰。

具体细节如下:

  1. 在主函数执行计算斐波那契数列的同时,animateProgressBar 函数在后台不断更新并显示进度条,每隔40毫秒更新一次。
  2. 当计算斐波那契数列的任务完成时,主函数打印结果。此时,主函数返回,程序结束。
  3. 由于主函数返回,程序退出,所有 goroutine 也会被终止,包括用于显示进度条的 goroutine

三、总结

本文深入探讨了 Go 语言中的 Goroutine 并发模型,我们来小结一下:

  • Goroutine 是 Go 语言的并发执行单元,相对于传统线程更加轻量级,使得 Go 能够高效地处理大规模并发任务。
  • 异步执行是 Goroutine 的亮点,它允许耗时操作在不阻塞主程序的情况下执行,适用于构建高响应性的网络服务和并发应用。
  • Goroutine 的创建非常简单,只需在函数调用前添加关键字 go,而无需手动管理线程。Go 运行时系统会自动进行调度。

理解和掌握 Goroutine 是成为 Go 语言并发编程专家的重要一步。通过充分利用 Goroutine ,你可以更好地发挥 Go 语言的并发优势,构建出高性能、高并发的应用程序。