GoLang之[协程第一话]协程是怎样的存在

GoLang之[协程第一话]协程是怎样的存在

1.为什么说协程是“用户态线程”?

我们已经知道线程是进程中的执行体,拥有一个执行入口、已经从进程虚拟地址空间中分配的栈,包括用户栈和内核栈
操作系统会记录现场控制信息。而线程获得cpu时间片以后才可以执行。Cpu,这里站指针、指令指针等寄存器都要切换到对应的线程。

在这里插入图片描述

如果线程自己又创建几个执行体,给他们各自指定执行入口。申请一些内存分配给他们用作执行栈,那么线程就可以按需调度这几个执行体了。为了实现这些执行体的切换,现场也需要记录他们的控制信息,包括ID、栈的位置、执行入口、地址、执行现场等等。

在这里插入图片描述

线程可以选择一个执行体来执行,此时cpu中指令指针就会指向这个执行体的执行入口,栈基和栈指针寄存器也会指向线程给他分配的执行栈。

在这里插入图片描述

要切换执行体时,需要先保存当前执行的执行现场。然后切换到另一个执行体。

在这里插入图片描述

通过同样的方式可以恢复到之前的执行体,这样就可以从上次中断的地方继续执行。这些由线程创建的执行体就是所谓的携程。因为用户程序不能操作内核空间,所以只能给协程分配用户栈。
操作系统对协程一无所知,所以协程又被称为用户态线程。

在这里插入图片描述

协程的思想很早就被提出来了,最初是为了解决编译器实现中的问题,后来相继出现了很多种实现方式,例如Windows中的纤程,再例如lua中的coroutine。

在这里插入图片描述

2.协程如何实现“主动让出”和“恢复”?

可无论被赋予什么样的名字,有着怎样的用法,在创建协程时都要指定执行入口,底层都会分配协程执行栈和控制信息,否则又该如何实现用户态调度呢?而让出执行权时,也都要保存执行现场,不然如何能够从中断处恢复执行?所以,协程思想的关键在于控制流的”主动让出“和”恢复“

在这里插入图片描述

每个协程拥有自己的执行栈,可以保存自己的执行现场,所以可以由用户程序按需创建携程,携程主动让出执行权时,会保存执行现场,然后切换到其他协程。协程恢复执行时,会根据之前保存的执行现场,恢复到中断前的状态继续执行。这样就通过协程实现了轻量又灵活的由用户态进行调度的多任务模型。

在这里插入图片描述

即便如此,协程依然风平浪静很多年。直到高并发成为主流趋势,瞬间抵达的海量请求,让多进程模型下内存资源捉襟见肘

在这里插入图片描述

让多线程模型下,内核(态)用户(态)两头忙,却依然疲于应对,协程这种灵活轻量的用户太调度模型便受到了广泛的关注,而真正让协程大放异彩的,是他在io多路复用中的应用。二者的结合,助力协程成为炙手可热的高并发解决方案

在这里插入图片描述

### 创建和管理 Golang 中的协程池 在 Golang 中,通过使用 `goroutine` 可以轻松启动并发任务。然而,在处理大量并发请求时,无限制地创建新的 `goroutine` 会消耗过多内存并可能导致系统崩溃。因此,引入了协程池的概念来有效管理和重用已有的 `goroutine` 实例。 #### 使用第三方库 ants 构建协程池 为了简化开发过程并提升效率,推荐采用成熟的开源项目如蚂蚁金服团队贡献的 **ants** 库[^3]。该库提供了简单易用的功能接口用于构建高性能的应用场景下的协程池解决方案: - 安装方式如下所示: ```bash go get github.com/panjf2000/ants/v2 ``` - 初始化一个固定大小的工作线程池实例,并设置最大容量以及超时回收机制等参数配置: ```go import ( "fmt" "github.com/panjf2000/ants/v2" ) // 创建具有默认选项的新协程池 pool, err := ants.NewPool(10) if err != nil { fmt.Println(err) } defer pool.Release() ``` - 提交任务到工作队列等待被执行;这里需要注意的是提交的任务应当是一个实现了 `func()` 接口类型的匿名函数或者方法指针形式: ```go for i := 0; i < 5; i++ { err := pool.Submit(func() { fmt.Printf("running task %d\n", i) }) if err != nil { fmt.Printf("submitting task error: %s\n", err) } } ``` 上述代码片段展示了如何利用 `ants` 来初始化一个包含十个工作者的最大数量限制的协程池,并向其中注入五个简单的打印语句作为测试负载。 #### 手动实现简易版协程池 如果不想依赖外部包,则可以根据实际需求自定义一套基础版本的协程池逻辑框架。下面给出了一种可能的设计思路及其对应的伪代码表示法: - 预先分配一定数目的空闲通道缓冲区用来存储待命状态下的子进程; - 当有新作业到来时尝试从中取出可用者立即投入运行直至完成后再放回原处供后续调用; - 若当前所有成员均处于忙碌之中则暂时阻塞住直到某位成员释放出来为止继续往下走。 ```go type Task func() var workerCount int = 10 // 工作者数目 var queue chan Task // 待办事项列表 initWorkerPools() { // 启动工人们循环监听来自外界的消息通知 for i := 0; i < workerCount; i++ { go func(wid int) { for tsk := range queue { tsk() } }(i + 1) } } addTaskToQueue(task Task) bool{ select { case queue <- task: return true default: log.Fatal("queue is full") return false } } ``` 此段示例仅作概念性说明之用途并不具备完整的功能特性,具体细节还需要读者自行完善补充[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GoGo在努力

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值