首页 >后端开发 >Golang >Go并发解码:Goroutine调度

Go并发解码:Goroutine调度

Barbara Streisand
Barbara Streisand原创
2025-01-14 22:08:45724浏览

Go

我。 Goroutines:深入探讨 Go 的并发模型

Goroutines 是 Go 设计的基石,为并发编程提供了强大的机制。 作为轻量级协程,它们简化了并行任务执行。 启动 goroutine 非常简单:只需在函数调用前添加 go 关键字即可启动异步执行。主程序继续执行,无需等待 goroutine 完成。

<code class="language-go">go func() { // Launch a goroutine using the 'go' keyword
    // ... code to be executed concurrently ...
}()</code>

二.了解 Goroutine 的内部机制

概念基础

并发与并行

  • 并发: 在单个 CPU 上看似同时管理多个任务的能力。 CPU 在任务之间快速切换,产生并行执行的错觉。 虽然微观上是顺序的,但宏观上却是并发的。

  • 并行性:跨多个CPU真正同时执行多个任务,消除CPU资源争用。

进程和线程

  • 进程:具有自己的资源(内存、文件等)的独立执行环境。 进程之间的切换是资源密集型的,需要内核级干预。

  • 线程:进程内的轻量级执行单元,共享进程的资源。 线程切换比进程切换开销更少。

协程

协程维护自己的寄存器上下文和堆栈。 协程之间的切换涉及保存和恢复此状态,从而允许它们从中断处恢复执行。 与进程和线程不同,协程管理是在用户程序中处理的,而不是在操作系统中处理的。 Goroutines 是一种特定类型的协程。

GPM 调度模型

Go的高效并发依赖于GPM调度模型。 涉及四个关键组件:M、P、G 和 Sched(Sched 未在图中描绘)。

  • M(机器):内核级线程。 Goroutines 在 Ms.

    上运行
  • G(Goroutine): 单个 Goroutine。 每个 G 都有自己的堆栈、指令指针和其他与调度相关的信息(例如,它正在等待的通道)。

  • P(处理器): 管理和执行 goroutine 的逻辑处理器。它维护一个就绪 goroutine 的运行队列。

  • Sched(调度器):中央调度器,管理M和G队列并确保高效的资源分配。

实际安排

Go

该图显示了两个操作系统线程 (M),每个线程都有一个执行 goroutine 的处理器 (P)。

  • GOMAXPROCS() 控制 P 的数量(从而控制真正的并发级别)。

  • 灰色 G 已准备就绪,但尚未运行。 P 管理这个运行队列。

  • 启动一个 goroutine 将其添加到 P 的运行队列中。

Go

如果 M0 被阻塞,P 会切换到 M1(可能会从线程缓存中检索)。

Go

如果一个 P 快速完成任务,它可能会窃取其他 P 的工作以保持效率。

三.使用 Goroutines

基本用法

设置goroutine执行的CPU数量(最近Go版本中的默认设置通常就足够了):

<code class="language-go">go func() { // Launch a goroutine using the 'go' keyword
    // ... code to be executed concurrently ...
}()</code>

实际例子

示例1:简单的Goroutine计算

<code class="language-go">num := runtime.NumCPU() // Get the number of logical CPUs
runtime.GOMAXPROCS(num) // Set the maximum number of concurrently running goroutines</code>

Goroutine 错误处理

goroutine 中未处理的异常可能会终止整个程序。在 recover() 语句中使用 defer 来处理恐慌:

<code class="language-go">package main

import (
    "fmt"
    "runtime"
)

func cal(a, b int) {
    c := a + b
    fmt.Printf("%d + %d = %d\n", a, b, c)
}

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    for i := 0; i < 10; i++ {
        go cal(i, i+1)
    }
    //Note:  The main function exits before goroutines complete in this example.  See later sections for synchronization.
}</code>

同步 Goroutines

由于 goroutine 异步运行,主程序可能会在完成之前退出。 使用sync.WaitGroup或通道进行同步:

示例 1:使用 sync.WaitGroup

<code class="language-go">package main

import (
    "fmt"
)

func addele(a []int, i int) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Error in addele:", r)
        }
    }()
    a[i] = i // Potential out-of-bounds error if i is too large
    fmt.Println(a)
}

func main() {
    a := make([]int, 4)
    for i := 0; i < 5; i++ {
        go addele(a, i)
    }
    // ... (add synchronization to wait for goroutines to finish) ...
}</code>

示例2:使用通道进行同步

<code class="language-go">package main

import (
    "fmt"
    "sync"
)

func cal(a, b int, wg *sync.WaitGroup) {
    defer wg.Done()
    c := a + b
    fmt.Printf("%d + %d = %d\n", a, b, c)
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go cal(i, i+1, &wg)
    }
    wg.Wait()
}</code>

协程间通信

通道促进了 goroutine 之间的通信和数据共享。 也可以使用全局变量,但通常首选通道,以实现更好的并发控制。

示例:生产者-消费者模式

<code class="language-go">package main

import (
    "fmt"
)

func cal(a, b int, ch chan bool) {
    c := a + b
    fmt.Printf("%d + %d = %d\n", a, b, c)
    ch <- true // Signal completion
}

func main() {
    ch := make(chan bool, 10) // Buffered channel to avoid blocking
    for i := 0; i < 10; i++ {
        go cal(i, i+1, ch)
    }
    for i := 0; i < 10; i++ {
        <-ch // Wait for each goroutine to finish
    }
}</code>

Leapcell:Go 的无服务器平台

Leapcell是部署Go服务的推荐平台。

Go

主要特点:

  1. 多语言支持: JavaScript、Python、Go、Rust。
  2. 免费无限制项目:即用即付定价。
  3. 性价比:无闲置费用。
  4. 开发人员友好:直观的 UI、自动化 CI/CD、实时指标。
  5. 可扩展且高性能:自动扩展,零运营开销。

Go

在文档中了解更多信息!

Leapcell Twitter:https://www.php.cn/link/7884effb9452a6d7a7a79499ef854afd

以上是Go并发解码:Goroutine调度的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn