go-GMP

这篇具有很好参考价值的文章主要介绍了go-GMP。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

go的协程和线程都绕不过GMP,关于GMP基本的工作流程,有go开发经验的大致都懂,这边更多关注GMP如何解决一些类似 协程饥渴的问题,以及底层的大致实现原理。

多线程循环

上篇讲了单线程是如何循环的,这里还是为 GMP的出场 大致介绍下。

工作模型

go-GMP

多个M都去全局G的队列中获取 g,所以,全局g的队列需要上锁。

改进版,增加本地队列

go-GMP

这样每个m都缓存了一个本地队列,避免每次都去全局队列里面拿,而且一次也能拿多个,降低了全局队列锁获取的开销。

这个其实就是 GMP了。P指的就是为M管理要执行的g

P 的定义

在runtime2.go中有定义:

 删除了很多源码,只留下和这里讲的相关的代码:
  type p struct {
    	id          int32 
        // M 线程
    	m           muintptr   // back-link to associated m (nil if idle)   
        // 可用的g的队列,进入不用加锁  嘚瑟
    	// Queue of runnable goroutines. Accessed without lock.
    	runqhead uint32 // 头尾
    	runqtail uint32
    	runq     [256]guintptr // 256 的容量 
    	runnext guintptr // 下一个可执行的g
    }

整体结构如下:

go-GMP

完整的GMP模型就如下:

go-GMP

和上面的改进版很像,只是抽象出来一个P,专门来管理

p的作用

1. M与G之间的中介

2. P持有一些G,使得每次获取G的时候不用从全局找

3. 大大减少了并发冲突的情况

代码的逻辑

要回到 schedule(),上篇中有提到:
schedule()中有为 M查找,要运行的g,通过这个方法:

 gp, inheritTime, tryWakeP := findRunnable() // blocks until work is available

 // Finds a runnable goroutine to execute.
// Tries to steal from other P's, get g from local or global queue, poll network.
// 查询一个 能运行的g去执行,尝试去别的p里面偷,或者从 本地、全局队列了获取。
//  poll network 涉及到了 epoll 和 poll 和这里没关系
func findRunnable() (gp *g, inheritTime, tryWakeP bool) {
    // local runq 本地拿 
	if gp, inheritTime := runqget(pp); gp != nil {
		return gp, inheritTime, false
	}
	// global runq 全局拿 
	if sched.runqsize != 0 {
		lock(&sched.lock) // 全局队列拿 需要上锁
		gp := globrunqget(pp, 0)
		unlock(&sched.lock)
        }
        // 如果还拿不到 就去偷 去别的p里面偷	
        gp, inheritTime, tnow, w, newWork := stealWork(now)
}

func runqget(pp *p) (gp *g, inheritTime bool) {
	// If there's a runnext, it's the next G to run.
	next := pp.runnext //从p的next获取的下一个

	if next != 0 && pp.runnext.cas(next, 0) {
		return next.ptr(), true
	}
}
源码都是未截取完整的。

看下全局怎么拿的:

//Try get a batch of G's from the global runnable queue.
// sched.lock must be held.
func globrunqget(pp *p, max int32) *g {
	assertLockHeld(&sched.lock)
	n := sched.runqsize/gomaxprocs + 1
	if n > sched.runqsize {
		n = sched.runqsize
	}
	if max > 0 && n > max {
		n = max
	}
	if n > int32(len(pp.runq))/2 {
		n = int32(len(pp.runq)) / 2
	}
	sched.runqsize -= n

	gp := sched.runq.pop()
	n--
	for ; n > 0; n-- {
		gp1 := sched.runq.pop()
		runqput(pp, gp1, false)
	}
	return gp
}

经过了一系列的计算,最终确定拿 n个g放回本地。 因为传过来的 max=0
所以 n 为 len(pp.runq)/ 2sched.runqsize/gomaxprocs + 1 中的数量大的那个。
runq [256]guintptr 声明为256
本地队列容量的一半 128,或者 全局队列的个数除以 gomaxprocs(p的个数) + 1,最多拿128个。

steal

上面逻辑讲了,如果全局也拿不到,就调用 stealWork 去别的p里面拿, 看下代码:

// stealWork attempts to steal a runnable goroutine or timer from any P.
func stealWork(now int64) (gp *g, inheritTime bool, rnow, pollUntil int64, newWork bool) {
  // 具体的逻辑,就不看了。就是遍历里面的p 从符合条件的p的本地队列,拿一半过去。
 }

小结:

如果在本地或者全局队列中都找不到G, 去别的P中“偷〞,增强了线程的利用率

协程新建

代码在 proc.go中

func newproc(fn *funcval) {
	gp := getg() 
	pc := getcallerpc()
	systemstack(func() {
       // 新创建一个 g
		newg := newproc1(fn, gp, pc)

		pp := getg().m.p.ptr() // 获取当前g 的 p 
		runqput(pp, newg, true) // 通过这函数,将g放置
	})
}

// getg returns the pointer to the current g.
func getg() *g

小结:

1. 优先获取当前的P 

(这点上有看到说是随机获取一个p,但是从 getg() 官方描述来看,是获取的当前p,如果这里我未理清楚,可以评论下)

2. 将新协程放入P的runnext(插队)
3. 若P本地队列满,放入全局队列

问题:

目前为止,已经解决了第一个问题

多线程并发时,会抢夺协程队列的全局锁

但是 协程顺序执行,无法并发 ,如一个协程执行时间非常长,一直占着m,就会导致其他g无法及时响应。文章来源地址https://www.toymoban.com/news/detail-747830.html

到了这里,关于go-GMP的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 关于进程、线程、协程的概念以及Java中的应用

    本文将从“操作系统”、“Java应用”上两个角度来探究这三者的区别。 在我本人的疑惑中,我有以下3个问题。 在“多道程序环境下”,允许多个程序并发执行,此时它们将失去封闭性,并具有间断性以及不可再现性的特征,因此需要引入进程的概念。 进程是程序执行的过

    2024年02月08日
    浏览(62)
  • Go有协程,Java有虚拟线程,聊一聊Java Virtual Threads

    文章首发地址 Java 19 中引入了 Virtual Threads,也称为 Virtualized Threads,是 Project Loom 的核心特性之一。Virtual Threads 是一种全新的轻量级线程实现方式,它可以在 Java 应用程序中实现高效的协程编程模型。 在传统的 Java 线程模型中,每个线程都会对应一个操作系统线程,这样会带

    2024年02月16日
    浏览(55)
  • Java/Python/Go不同开发语言在进程、线程和协程的设计差异

    在多线程项目开发时,最常用、最常遇到的问题是 1,线程、协程安全 2,线程、协程间的通信和控制 本文主要探讨不同开发语言go、java、python在进程、线程和协程上的设计和开发方式的异同。 进程 进程是 操作系统进行资源分配的基本单位,每个进程都有自己的独立内存空

    2024年01月23日
    浏览(50)
  • GO语言GMP模型

    目录 程序入口  协程主动让出:  被动让出:  schedule 监控线程 在执行一系列检查和初始化(创建多少个P,与M0关联)后,进入runtime.main,创建main goroutine,执行mian.mian。  一开始GO语言的调度只有M和G。每个M获取G都要加锁。所以加入了P。一个P关联一个M,这样就可以从P的本地

    2024年02月16日
    浏览(44)
  • go-GMP

    go的协程和线程都绕不过GMP,关于GMP基本的工作流程,有go开发经验的大致都懂,这边更多关注GMP如何解决一些类似 协程饥渴的问题,以及底层的大致实现原理。 上篇讲了单线程是如何循环的,这里还是为 GMP的出场 大致介绍下。 工作模型 多个M都去全局G的队列中获取 g,所

    2024年02月05日
    浏览(56)
  • python正则+多线程(代理)+线程池+协程

    常用元字符 . 匹配除换行符以外的任意字符 w 匹配字幕或数字或下划线 s 匹配任意空白字符 d 匹配数字 n 匹配一个换行符 t 匹配一个制表符 W 匹配非字母或数字或下划线 D 匹配非数字 S 匹配非空白符 a|b 匹配字符a或b () 匹配括号内的表达式,也表示一个组 […] 匹配字符

    2024年02月16日
    浏览(45)
  • Go简单实现协程池

    首先就是进程、线程、协程讲解老三样。 进程:  本质上是一个独立执行的程序,进程是操作系统进行资源分配和调度的基本概念,操作系统进行资源分配和调度的一个独立单位。 线程:  是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单

    2024年02月08日
    浏览(43)
  • go 协程并发数控制

    错误的写法: 这里的-ch 是为了从channel 中读取 数据,为了不使channel通道被写满,阻塞 go 协程数的创建。但是请注意,go workForDraw(v, wg) 是不阻塞后续的-ch 执行的,所以就一直go workForDraw(v, wg) 拉起新的协程。这么是达不到控制协程并发数10 的目的 正确的写法: 直接将-ch 写入

    2024年02月12日
    浏览(35)
  • 线程 进程 协程 区别

    在并发编程中,\\\"线程\\\"和\\\"协程\\\"都是用于实现并发执行的概念,但它们有一些重要的区别。 线程(Thread): 线程是操作系统的概念,是操作系统调度的最小执行单位,是进程中的一个实体,表示程序执行的基本单元。 线程由操作系统内核调度和管理,它拥有自己的执行上下文

    2024年02月04日
    浏览(64)
  • 什么是进程、线程、协程

    我们都知道计算机的核心是CPU,它承担了所有的计算任务;而操作系统是计算机的管理者,它负责任务的调度、资源的分配和管理,统领整个计算机硬件;应用程序则是具有某种功能的程序,程序是运行于操作系统之上的。 进程是一个具有一定独立功能的程序在一个数据集上

    2024年02月13日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包