Golang GMP模型

Golang GMP模型

GMP 是 Go 语言运行时(runtime)中的一个重要组件,它是 Go 语言的调度模型。GMP 模型使用三种不同的线程来处理 Go 程序:Goroutine、M(Machine)和 P(Processor)。在 GMP 模型中,Goroutine 是实际编写的程序代码,M 是 Go 语言的内部线程,P 则负责管理 M 和 Goroutine 之间的调度。

解释一下 GMP 模型的工作原理:

GMP 模型的核心是三个概念:G、M 和 P。

  • G:Goroutine是Go语言中的轻量级协程,它可以被看作是一种用户态的线程,能够在单个线程上模拟出多个线程的并发执行效果。Goroutine的调度由M和P共同协作完成。

  • M:Machine是操作系统线程的简称,它是Go语言实现协程调度的实际执行者。每个M都有一个Goroutine队列,以及一个P调度器,用于管理并调度Goroutine。M的数量可以通过GOMAXPROCS环境变量进行配置,默认为CPU核心数。

  • P:Processor是调度器的简称,它负责将Goroutine绑定到M线程上,并将Goroutine放入队列中,等待M线程运行。每个P都有一个Goroutine队列,以及一个M调度器,用于管理并调度M线程。P的数量是由Go运行时自动根据需要进行调整的。

GMP模型的本质是将一组Goroutine分配到一组M线程中,通过P调度器来管理并调度它们的执行。当一个Goroutine被创建时,它会首先被放入到P的队列中,等待M线程来执行。M线程会从P的队列中取出一个Goroutine,并将其执行,如果执行过程中发生阻塞,M线程会放弃该Goroutine的执行,并将其重新放回到P的队列中,等待后续的调度。

在GMP模型中,调度器P有两个重要的任务,一是负责调度M线程的执行,二是负责调度Goroutine的执行。P会通过两个队列来分别管理M线程和Goroutine,M线程队列中存储等待调度的M线程,Goroutine队列中存储等待调度的Goroutine。P会按照一定的调度算法,从M线程队列中选取一个M线程,并将其绑定到一个Goroutine上执行。

具体来说,Goroutine 是 Go 语言中的并发基本单元,一个 Goroutine 类似于一条轻量级的线程,它由 Go 运行时负责调度和管理。Goroutine 是 Go 语言的一大特色,因为它具有极低的启动和销毁成本,一个 Go 程序可以同时创建数百万个 Goroutine,而这些 Goroutine 的调度则由 Go 运行时自动进行,无需程序员手动管理。

M 是 Go 运行时对操作系统线程的抽象,每个 M 负责管理一条操作系统线程以及其中运行的 Goroutine。每个 M 与一个或多个 P 相关联,P 是 Goroutine 调度器,它负责在 M 上运行 Goroutine,并将其调度到其他 M 上执行。每个 P 都有一个队列,用于存放可运行的 Goroutine。

P 由调度器创建,并且与 M 绑定。P 的数量可以通过 GOMAXPROCS 环境变量设置。当一个 Goroutine 准备就绪后,调度器会将其加入到某个 P 的队列中,P 就会负责运行这个 Goroutine,直到其执行完毕或者被阻塞。

总的来说,GMP 模型实现了一个三层的调度器,其中最上层是 Goroutine 调度器,用于调度 Goroutine;第二层是 P 调度器,用于将可运行的 Goroutine 调度到某个 P 上执行;第三层是 M 调度器,用于将 P 调度到某个 M 上运行。

在 GMP 模型中,调度器的核心是 Work-stealing 调度算法。这个算法可以保证 Goroutine 在不同 P 上的负载均衡,并且能够充分利用多核 CPU 的性能。

在实践中,GMP 模型让 Go 语言具有了出色的并发性能和扩展性。程序员只需要编写简单的 Goroutine 代码,就可以充分利用多核 CPU 并发执行任务。

总的来说,GMP模型通过将Goroutine分配到一组M线程中,并由P调度器管理和调度,实现了高效的协程调度。Goroutine通过轻量级的栈和调度器管理,大大提高了程序的并发能力和执行效率。

GMP 模型的设计目标是充分利用多核 CPU,提高并发程序的性能。具体来说,GMP 模型可以通过以下几种方式实现:

  1. 多线程执行:GMP 模型中的 M 可以同时运行多个 Goroutine,充分利用多核 CPU 的计算能力。
  2. 多态调度:P 可以将 Goroutine 分配给任意一个 M,实现了多态调度,充分利用了计算资源。
  3. 伸缩性:GMP 模型可以根据系统的负载情况动态调整 M 和 P 的数量,实现伸缩性。

下面是一个简单的 GMP 模型的例子,演示了如何使用 GMP 模型实现并发处理任务:

package main

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

func main() {
	runtime.GOMAXPROCS(1) // 设置使用一个处理器核心
	var wg sync.WaitGroup // 定义一个同步等待组
	wg.Add(2)             // 有两个任务需要完成

	fmt.Println("Start Goroutines")

	go func() { // 启动一个新的 goroutine
		defer wg.Done() // 在函数退出时调用 wg.Done(),通知等待组一个任务已完成
		for count := 0; count < 3; count++ {
			for char := 'a'; char < 'a'+26; char++ {
				fmt.Printf("%c ", char)
			}
			fmt.Println()
		}
	}()

	go func() { // 启动另一个 goroutine
		defer wg.Done() // 在函数退出时调用 wg.Done(),通知等待组一个任务已完成
		for count := 0; count < 3; count++ {
			for char := 'A'; char < 'A'+26; char++ {
				fmt.Printf("%c ", char)
			}
			fmt.Println()
		}
	}()

	fmt.Println("Waiting To Finish")
	wg.Wait() // 等待所有 goroutine 完成

	fmt.Println("\nTerminating Program")
}

Work-stealing 调度算法

package main

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

func main() {
	numCPUs := runtime.NumCPU() // 获取可用处理器核心数
	runtime.GOMAXPROCS(numCPUs) // 设置使用所有处理器核心

	work := make(chan int, numCPUs) // 创建一个带有缓冲的int类型通道

	var wg sync.WaitGroup // 定义一个同步等待组

	for i := 0; i < numCPUs; i++ {
		wg.Add(1) // 为每个处理器核心增加一个任务
		go func() { // 启动每个处理器核心
			defer wg.Done()
			for {
				select {
				case job, ok := <-work: // 从通道中获取工作
					if ok {
						fmt.Println("Worker", i, "processing job", job)
						time.Sleep(time.Second) // 模拟工作时间
					} else {
						fmt.Println("Worker", i, "shutting down")
						return
					}
				default: // 如果通道中没有工作,则寻找其他处理器核心的闲置工作
					runtime.Gosched()
				}
			}
		}()
	}

	for i := 0; i < numCPUs*2; i++ { // 向通道中添加工作
		work <- i
	}

	close(work) // 关闭通道,表示所有工作都已完成

	wg.Wait() // 等待所有处理器核心完成任务

	fmt.Println("All work complete")
}

在此示例中,首先使用runtime.NumCPU()获取可用的处理器核心数,并使用runtime.GOMAXPROCS()将所有处理器核心设置为可用状态。创建了一个带有缓冲的通道,大小为可用的处理器核心数,以便可以将工作项分发给多个处理器核心。使用sync.WaitGroup来等待所有处理器核心完成任务。在for循环中,为每个处理器核心启动一个goroutine,每个goroutine使用select语句从通道中获取工作。如果通道中有工作,则处理工作,否则通过runtime.Gosched()在处理器核心之间进行工作窃取。在主函数中,向通道中添加工作,并在完成所有工作后关闭通道。最后,调用wg.Wait()等待所有处理器核心完成任务,并打印"All work complete"表示所有工作都已完成。
posted @ 2023-03-30 10:41  紫系流月  阅读(518)  评论(0编辑  收藏  举报