Go

Golang的特性九(GMP调度模型)----高并发的核心机制

Royal
2023-08-05 / 0 评论 / 7 阅读 / 正在检测是否收录...

1. 协程由来
进程
通常表示计算机中正在运行的程序实例。

线程
通常表示内核级的线程。计算机中最小可执行单元

协程

线程分为内核级线程和用户级线程,一个或多个用户级线程要绑定一个内核级线程,其中内核级线程依然叫线程(thread),而用户级线程叫协程(co-routine)。
协程跟线程是有区别的,线程由 CPU 调度是抢占式的,协程由用户态调度是协作式的,一个协程让出 CPU 后,才执行下一个协程,在go语言中,协程叫做goroutine,一个goroutine初始只占几KB,但实际是可伸缩的,如果需要更多内容,runtime 会自动为 goroutine 分配,因此调度起来非常方便,支持大量的goroutine

2. 什么是GMP模型?

GMP模型是Go语言并发模型的核心,它由三个主要组件组成:

  • G(Goroutine):Goroutine是Go语言中的轻量级线程,由Go运行时管理。Goroutine的创建和销毁成本很低,可以轻松创建成千上万个Goroutine。
  • M(Machine):M代表操作系统线程(OS Thread),由操作系统管理。M负责执行Goroutine的代码。
  • P(Processor):P是Go运行时抽象出来的处理器,它负责调度Goroutine到M上执行。P的数量通常等于CPU核心数,可以通过GOMAXPROCS环境变量来设置。
    m7t1pblf.png

3. GMP模型的工作原理

  • Goroutine的创建:当创建一个Goroutine时,它会被放入一个全局队列或本地队列中,等待被调度执行。
  • 调度器:Go运行时调度器会从队列中取出Goroutine,并将其分配给一个P。
  • 执行:P会将Goroutine绑定到一个M上,M是操作系统线程,负责执行Goroutine的代码。
  • 上下文切换:当一个Goroutine阻塞(如等待I/O操作)时,调度器会将其从M上解绑,并调度另一个Goroutine到该M上执行。
  • 负载均衡:调度器会确保所有的P都尽可能均匀地分配Goroutine,以实现负载均衡。

下面通过一个简单的代码示例来演示GMP模型的工作原理。

package main

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

func main() {
    // 设置使用的CPU核心数
    runtime.GOMAXPROCS(2)

    var wg sync.WaitGroup

    // 创建10个Goroutine
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            fmt.Printf("Goroutine %d started\n", id)
            time.Sleep(time.Second) // 模拟耗时操作
            fmt.Printf("Goroutine %d finished\n", id)
        }(i)
    }

    // 等待所有Goroutine完成
    wg.Wait()
    fmt.Println("All Goroutines finished")
}

代码解析

  • 设置CPU核心数:通过runtime.GOMAXPROCS(2)设置使用的CPU核心数为2,这意味着最多有2个P(Processor)在调度Goroutine。
  • 创建Goroutine:使用go关键字创建10个Goroutine,每个Goroutine都会打印开始和结束的信息,并模拟一个耗时操作(time.Sleep)。
  • WaitGroup:使用sync.WaitGroup来等待所有Goroutine完成。
  • 输出结果:运行程序后,你会看到10个Goroutine被调度执行,但由于我们设置了2个CPU核心,所以最多只有2个Goroutine在同时执行。
P的数量和M的数量的确定

P的数量:由启动时环境变量GOMAXPROCS来决定的,一般设置GOMAXPROCS也不会超过系统的核数
M的数量:go程序启动时,会设置M的最大数量,默认10000。但是内核很难创建出如此多的线程,因此默认情况下M的最大数量取决于内核

0

评论

博主关闭了当前页面的评论