go语言GMP模式介绍以及协程案例展示

2024-01-17 19:20

本文主要是介绍go语言GMP模式介绍以及协程案例展示,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一. MPG模式

Go语言的调度模型被称为GMP,这是一个高效且复杂的调度系统,用于在可用的物理线程上调度goroutines(Go的轻量级线程)。GMP模型由三个主要组件构成:Goroutine、M(机器)和P(处理器)。下面详细介绍这三个组件以及它们如何协同工作。

1. Goroutine(G)

  • Goroutine 是Go语言中的一个基本概念,类似于线程,但比线程更轻量。Goroutines在Go的运行时环境中被调度和管理,而非操作系统。
  • Goroutines非常轻量,启动快,且切换开销小。这是因为它们有自己的栈,这个栈可以根据需要动态增长和缩减。

2. Machine(M)

  • M 代表了真正的操作系统线程。每个M都由操作系统调度,并且拥有一个固定大小的内存栈用于执行C代码。
  • M负责执行Goroutines的代码。Go的运行时会尽量复用M,以减少线程的创建和销毁带来的开销。

3. Processor(P)

  • P 是Go运行时的一个资源,可以看作是执行Goroutines所需的上下文环境。P的数量决定了系统同时运行Goroutines的最大数量。
  • 每个P都有一个本地的运行队列,用于存放待运行的Goroutines。
  • P的数量一般设置为等于机器的逻辑处理器数量,以充分利用多核的优势。

MPG 工作方式

  • 在程序启动时,Go运行时会根据可用的核心数创建一定数量的P。
  • 每个P都会与一个M绑定在一起,这个M会从P的本地运行队列中取出一个G来执行。
  • 当Goroutine阻塞时(比如等待I/O),执行它的M会被解绑,并且该Goroutine会被移动到全局队列或者等待队列中,让其他M可以接管这个P并继续执行其他Goroutines。
  • 如果所有的M都阻塞了,运行时会创建额外的M来保证至少有一个M是非阻塞的,以继续执行Goroutines。

调度优势

  • Go调度器的设计使得成千上万的Goroutines能够在数量较少的线程(M)上高效运行,这极大地减少了上下文切换的开销。
  • Go的调度器是协作式的,这意味着Goroutines需要自己释放控制权。通常,这发生在显式的阻塞操作(如I/O操作)或者隐式的调度点(如函数调用)时。

二.互斥锁实现阶乘计算

1.代码

package mainimport ("fmt""sync"
)// 多协程计算阶乘var (myMap = make(map[int]int) // 全局变量mu    sync.Mutex          // 安全访问myMapwg    sync.WaitGroup      // 等待所有协程完成
)func main() {// 开启协程for i := 1; i <= 10; i++ {wg.Add(1)go factorial(i)}// 等待所有协程完成后再打印wg.Wait()// 遍历myMap并打印结果for i, v := range myMap {fmt.Println(i, v)}
}// 计算阶乘
func factorial(n int) {res := 1// 计算阶乘for i := 1; i <= n; i++ {res *= i}// 递延地减少WaitGroup计数器defer wg.Done()// 在修改myMap之前进行互斥锁操作mu.Lock()myMap[n] = resmu.Unlock()
}

2.MPG模型解释

Go语言的并发模型被称为MPG模型,其中:

  • M 代表机器(Machine),是对操作系统线程的抽象。
  • P 代表处理器(Processor),是对M进行调度的上下文。
  • G 代表Goroutine,是Go的轻量级线程,它在M上执行。

当一个Go程序运行时:

  1. Goroutines(G) 在**Processors(P)**上被调度。每个P都绑定到一个M(操作系统线程),但一个P可以调度多个G。
  2. 在这段代码中,当main函数启动多个goroutines时,这些G被分配到不同的P上,并且可能在不同的M上执行。
  3. 当一个G在执行阶乘计算时,如果它需要等待(例如,等待互斥锁),它会被P挂起,并且P会转而执行另一个G。
  4. 一旦所有的G都执行完毕(即wg.Wait()返回),程序进入最后阶段,遍历并打印myMap中存储的结果。

3.管道channel基本概念

创建管道
  • 使用make关键字创建管道。可以创建有缓冲的管道或无缓冲的管道。
  • 示例:ch := make(chan int) 创建一个传递整型数据的无缓冲管道。
  • 无缓冲管道:这种管道没有存储空间,因此发送操作(ch <- v)会阻塞,直到另一端有goroutine准备好接收(<-ch)。无缓冲管道确保同时只有一个数据在通道中传递,它强制发送者和接收者同步交换数据。
  • 有缓冲管道:这种管道有一个指定的容量,允许在接收者准备好接收之前存储有限数量的值。如果管道满了(即达到其容量限制),发送操作将阻塞;如果管道为空,接收操作将阻塞。有缓冲管道提供了一定程度的松耦合,允许发送者和接收者在缓冲区不满和不空的情况下独立操作。
发送和接收数据
  • 使用箭头操作符(<-)来发送和接收数据。
  • 示例:ch <- v 表示将值v发送到管道chv := <-ch 表示从管道ch接收值并赋给变量v
无缓冲与有缓冲
  • 无缓冲管道:发送操作会阻塞,直到另一端有goroutine进行接收操作。
  • 有缓冲管道:只有当缓冲区满时发送操作才会阻塞,只有当缓冲区空时接收操作才会阻塞。
使用场景
  • 同步:管道可用于不同goroutines之间的同步。
  • 数据共享:通过管道安全地在goroutines之间传递数据,防止竞争条件。
  • 流程控制:通过有缓冲管道控制处理速度和压力。
重要特性
  • 安全性:管道在内部实现了必要的同步机制,因此在多个goroutines访问时是安全的。

  • 阻塞性:无缓冲管道在发送或接收时会阻塞,直到另一端准备好。

  • 关闭管道

  • 使用close函数关闭管道。

  • 关闭管道后,不能再向管道发送数据,但仍可以接收管道中已存在的数据。

  • 尝试向已关闭的管道发送数据会引发panic。

  • 范围循环:可以使用for range循环从管道接收数据,直到管道被关闭。

注意事项
  • 死锁:如果不正确使用管道,特别是在管道操作之间没有适当的同步时,可能导致死锁。
  • 资源管理:应确保在不再需要时关闭管道,以避免内存泄漏。

4.管道channel实现阶乘计算

package mainimport ("fmt""sync"
)var (ch  = make(chan int) // FIFO 队列 first in first out 线程安全wg2 sync.WaitGroup   // 用于等待所有goroutine完成
)func main() {for i := 1; i <= 10; i++ {// 添加WaitGroup的计数wg2.Add(1)go calChannel(i)}wg2.Wait() // 等待所有goroutine完成close(ch)  // 关闭通道// 启动一个新的goroutine来打印管道中的值go func() {for v := range ch {fmt.Println(v)}}()}func calChannel(n int) {defer wg2.Done() // 在函数退出时通知WaitGroup// 通过通道计算阶乘res := 1for i := 1; i <= n; i++ {res *= i}ch <- res
}

三. interface{}类型

package mainimport "fmt"func main() {ch := make(chan interface{}, 3)ch <- 88ch <- "i am god"cat := Cat{Name: "小花猫", Age: 4}ch <- catclose(ch)// 丢弃管道中的值<-ch<-chv := <-chfmt.Printf("%T", v)fmt.Println()// 需要类型断言 ∵从管道中取出的值类型实际是interface{}类型 只有空接口类型才可以类型断言val := v.(Cat)fmt.Printf("%v", val.Name)}type Cat struct {Name stringAge  int
}

这篇关于go语言GMP模式介绍以及协程案例展示的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/616952

相关文章

Python爬虫selenium验证之中文识别点选+图片验证码案例(最新推荐)

《Python爬虫selenium验证之中文识别点选+图片验证码案例(最新推荐)》本文介绍了如何使用Python和Selenium结合ddddocr库实现图片验证码的识别和点击功能,感兴趣的朋友一起看... 目录1.获取图片2.目标识别3.背景坐标识别3.1 ddddocr3.2 打码平台4.坐标点击5.图

使用Go语言开发一个命令行文件管理工具

《使用Go语言开发一个命令行文件管理工具》这篇文章主要为大家详细介绍了如何使用Go语言开发一款命令行文件管理工具,支持批量重命名,删除,创建,移动文件,需要的小伙伴可以了解下... 目录一、工具功能一览二、核心代码解析1. 主程序结构2. 批量重命名3. 批量删除4. 创建文件/目录5. 批量移动三、如何安

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

Go路由注册方法详解

《Go路由注册方法详解》Go语言中,http.NewServeMux()和http.HandleFunc()是两种不同的路由注册方式,前者创建独立的ServeMux实例,适合模块化和分层路由,灵活性高... 目录Go路由注册方法1. 路由注册的方式2. 路由器的独立性3. 灵活性4. 启动服务器的方式5.

Go语言中三种容器类型的数据结构详解

《Go语言中三种容器类型的数据结构详解》在Go语言中,有三种主要的容器类型用于存储和操作集合数据:本文主要介绍三者的使用与区别,感兴趣的小伙伴可以跟随小编一起学习一下... 目录基本概念1. 数组(Array)2. 切片(Slice)3. 映射(Map)对比总结注意事项基本概念在 Go 语言中,有三种主要

使用Navicat工具比对两个数据库所有表结构的差异案例详解

《使用Navicat工具比对两个数据库所有表结构的差异案例详解》:本文主要介绍如何使用Navicat工具对比两个数据库test_old和test_new,并生成相应的DDLSQL语句,以便将te... 目录概要案例一、如图两个数据库test_old和test_new进行比较:二、开始比较总结概要公司存在多

Go Mongox轻松实现MongoDB的时间字段自动填充

《GoMongox轻松实现MongoDB的时间字段自动填充》这篇文章主要为大家详细介绍了Go语言如何使用mongox库,在插入和更新数据时自动填充时间字段,从而提升开发效率并减少重复代码,需要的可以... 目录前言时间字段填充规则Mongox 的安装使用 Mongox 进行插入操作使用 Mongox 进行更

Java实现状态模式的示例代码

《Java实现状态模式的示例代码》状态模式是一种行为型设计模式,允许对象根据其内部状态改变行为,本文主要介绍了Java实现状态模式的示例代码,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来... 目录一、简介1、定义2、状态模式的结构二、Java实现案例1、电灯开关状态案例2、番茄工作法状态案例

C语言中自动与强制转换全解析

《C语言中自动与强制转换全解析》在编写C程序时,类型转换是确保数据正确性和一致性的关键环节,无论是隐式转换还是显式转换,都各有特点和应用场景,本文将详细探讨C语言中的类型转换机制,帮助您更好地理解并在... 目录类型转换的重要性自动类型转换(隐式转换)强制类型转换(显式转换)常见错误与注意事项总结与建议类型

Go语言利用泛型封装常见的Map操作

《Go语言利用泛型封装常见的Map操作》Go语言在1.18版本中引入了泛型,这是Go语言发展的一个重要里程碑,它极大地增强了语言的表达能力和灵活性,本文将通过泛型实现封装常见的Map操作,感... 目录什么是泛型泛型解决了什么问题Go泛型基于泛型的常见Map操作代码合集总结什么是泛型泛型是一种编程范式,允