Go并发编程#协程

本文介绍了Go语言的协程概念,对比了协程与线程的差异,重点讲解了GPM调度模型,包括G、P和M的角色,以及协程创建、运行和调度的工作流程。Go协程以其高效、易用和安全的特点,提供了一种高性能并发编程方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是协程

Go的并发编程是通过协程(goroutine)来实现的,所以了解协程是掌握Go并发编程的第一步。协程是更加轻量级的线程,当我们需要异步执行一段代码的时候,直接创建一个协程来运行接口,Go的协程是通过运行时(runtime)来管理的,所以开发人员不需要关心协程的调度以及销毁等过程。

创建协程方法:go + 匿名函数

package async

import "testing"

func TestGoroutine(t *testing.T) {

    //创建一个协程异步执行任务
	go func() {
		println("do something")
	}()


	println("end")
}

协程和线程的区别

  1. 占用空间:协程比线程占用更小的空间,线程和协程占用空间主要是栈占用的空间大小,创建一个线程占用内存一般是在1M到数M之间,而一个协程默认的站大小是2K,这是因为协程的栈是可以进行扩展的,初始化的时候不需要分配太多,这样更加节省资源。所以在项目中创建的线程数往往不能太多,但是却可以创建成千上万的协程。
     
  2. 调度方式:这个是协程和线程最大的区别,也是我认为Go做的很好的一个设计。我们知道传统的线程是对应这操作系统的线程,然后是由操作系统来进行调度的,进行CPU时间片的抢占。但是Go的协程是由用户态的调度器来实现调度,不直接和操作系统线程关联,因为是用户态,所以在调度的时候线程切换的开销更小,不存在状态的切换。
     
  3. 并发度和性能:其实从1和2两个区别也能看出来,Go协程的并发性能比传统的线程要高很多,因为Go协程的创建和调度开销小,可以轻松创建成千上万个协程。而传统线程的并发度受限于系统的线程数目,创建大量线程可能会导致系统资源耗尽。当然这个更多的是得益于Go的协程调度模型,这个在下面会讲到。

总的来说,Go协程提供了一种更加高效、易用且安全的并发编程方式。通过协程,开发者可以编写出高并发、高性能的程序,而不用担心传统多线程编程中可能出现的各种问题。

G-P-M调度模型

什么是GPM

Go协程的并发高性能主要还是得益于高效的调度策略,也就是我们这里要讲到的GPM模型,了解了这个模型的工作原理,我们也就掌握了Go中协程并发运行的原理。

  • G:代表 Goroutine,存储了 Goroutine 的执行栈信息、Goroutine 状态以及 Goroutine 的任务函数等。
     
  • P:代表逻辑 processor,P 的数量决定了系统内最大可并行的 G 的数量,P 的最大作用还是其拥有的各种 G 对象队列、链表、一些缓存和状态;G的执行依赖P,只有被分配到P中才能被执行。
     
  • M:M 代表着真正的执行计算资源。在绑定有效的 P 后,进入一个调度循环,而调度循环的机制大致是从 P 的本地运行队列以及全局队列中获取 G,切换到 G 的执行栈上并执行 G 的函数,调用 goexit 做清理工作并回到 M,如此反复。M 并不保留 G 状态,这是 G 可以跨 M 调度的基础。

依赖条件:

  • G必须依赖于P才能被M执行,程序创建的goroutine协程必须放入调度器P才能被执行。
  • M必须关联到一个P才能执行对应队列中的G,一个P可以被多个M关联,但是同一时刻只可能被一个M关联,所以这里可以看出,Go的并发度是取决于P的数量的,而不是M。

工作流程

创建一个协程到运行的简单过程:

GPM整体工作流程:

调度策略

队列轮转

从上面的GPM流程中我们可以看到每个P是维护了一个自己的协程队列,调度器会按照队列的出队顺序把G给到M去执行,每个G都会被分到一个时间片,执行一段时间后如何还未完成就记录上下文和寄存器信息,放入队列尾部等到下次重新被调度。这种是比较常规的调用方式。

抢占式调度

Go的协程调度是支持抢占式调度的,即一个Goroutine在执行过程中可能被强制暂停,切换到其他Goroutine执行。这种机制防止了某个Goroutine长时间占用处理器,确保其他Goroutines也有机会执行。

系统调用

当一个Goroutine进入系统调用后会进入阻塞状态,这个时候其对应的M也会随之进入空闲状态,和P进行解绑。调度器会把P调度给其他的M,保证P中其他的G可以被执行。当系统调用完成之后,这个Goroutine会尝试获取空闲的P,如果获取到就继续执行,如果没有就进入全局队列,等待被执行。

工作量窃取

每个P维护的G队列可能不是均衡的,有的多有的少,为了提高并发度,这里提供了工作窃取的调度方式,跟线程的fork-join有点类似。当某个P执行中的G被执行完成之后,会先去全局队列中查询,如果全局队列中也没有就会从其他的P中”窃取“一部分G(一半儿)来执行。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值