逐步学习Go-协程goroutine

file

参考:逐步学习Go-协程goroutine – FOF编程网

什么是线程?

简单来说线程就是现代操作系统使用CPU的基本单元。线程基本包括了线程ID,程序计数器,寄存器和线程栈。线程共享进程的代码区,数据区和操作系统的资源。

线程为什么很“重”

因为线程会有很多上下文,操作系统需要调度线程执行不能让一个线程执行完才执行另一个线程(那其他线程就“饿死”了)。
线程调度就涉及线程切换:停止当前正在运行的线程,保存线程的状态(上下文),选择另一个线程并加载这个上下文并执行这个线程。线程切换比较耗时因为内核或者操作系统级别的线程有很多上下文,主要涉及的切换有:

  1. 程序计数器
  2. 寄存器
  3. CPU缓存
  4. CPU调度
  5. 线程状态管理

所以线程切换比较耗时。

什么是协程?

协程并不是一个新概念了,协程已经被很多语言使用了,比如:Java 19的VirtualThread,Python的asyncio, JavaScript ES6中的async/await, C#, Kotlin,…

协程就是轻量级线程,协程和操作系统的重量级线程的关系是M:N,一般M\<N。减少调度和切换开销。
协程还有什么优势?

  1. 内存占用小,据Go说,Go创建一个协程只需要2KB内存
  2. 切换成本低,线程切换只涉及用户程序的调度,不涉及线程哪些切换的内容,所以很快
  3. 创建销毁快,用户程序创建和销毁,所以很快

协程和线程的映射关系

我们可以把线程是协程的CPU,协程需要执行需要调度到某个线程上执行。
协程最终还是使用线程来执行,所以协程需要对应一个线程来执行自己的代码,那么这个映射关系是什么?

  1. 一对一
  2. 一对多
  3. 多对一
  4. 多对多

一对一

如何来理解一对一关系?我觉得这是在某一时刻,一个协程都由一个线程来管理和执行。

一对多

如果理解一对多关系?我觉得这是在一个时间段内,一个协程可能会被调度到多个线程上执行,但是在某一个时间点一个协程不会被调度到两个或者更多线程执行。

多对一

如何理解多对一关系?我觉得是多个协程在一个时间段内会被调度到同一个线程执行。

多对多

协程运行时是M:N模型,就是M个协程映射到N个线程上。

Go中的协程

进入正题,Go中提供了协程模型和API,没有可以直接操作的线程模型和API。

Go的协程特性

Go的协程遵守我们上面提到的协程特性:

  1. 轻量级
  2. 并发执行
  3. 异步执行
  4. 复用:这个复用指的是复用操作系统线程
  5. 协程之间通过Channel通信和同步
  6. 非抢占式调度:Go的协程调度器使用的是非抢占式调度模型,这就表示协程在运行期间是不可中断的,只有协程自己让出CPU,比如:协程休眠,I/O之类的操作协程才会让出CPU
  7. 高效上下文切换
  8. 优雅关闭
  9. 不阻塞主线程,主线程退出,协程也会退出

环境

我们使用go testing和testify来编写测试用例进行协程特性演示。
testify直接使用go get安装就可以了。

go get github.com/stretchr/testify

COPY

这是import的模块:

import ("fmt""runtime""testing""github.com/stretchr/testify/assert"
)

COPY

创建协程

go中创建协程不需要写接口,不需要写struct,只需要一个go关键字+执行函数就可以了。

  1. go+标准函数
  2. go+闭包/匿名函数
  3. go+方法(struct)
  4. interface{}+反射
  5. 如有其他方式,请留言告知

go+标准函数创建协程

我们先来创建一个Go函数,参数传入一个channel方便我们对channel进行同步控制:

// 标准Go函数
func standardFunc(ch chan bool) {println("Hello, Standard Function Go Routine")ch <- true
}

COPY

我们来创建一个Go协程来执行这个标准函数:

// 标准函数创建协程
func TestRoutine_ShouldSuccess_WhenCreateWithStandardFunction(t *testing.T) {ch := make(chan bool)// func为标准函数go standardFunc(ch)ret := <-chassert.True(t, ret)
}

COPY

执行截图:

file

go+闭包/匿名函数创建协程

这种方式比较方便:

// 闭包/匿名函数创建协程
func TestRoutine_ShouldSuccess_WhenCreateWithAnonymousFunction(t *testing.T) {ch := make(chan bool)// func为闭包/匿名函数go func() {println("Hello, Anonymous Function Go Routine")ch <- true}()ret := <-chassert.True(t, ret)
}

COPY

执行截图:

file

go+方法(struct)创建协程

我们先定义一个struct,struct有一个channel方便我们进行等待协程执行完成:

type s struct {ch chan bool
}

COPY

我们定义两个方法:run和wait,run来执行业务,wait等待run执行完成:

type s struct {ch chan bool
}func (s *s) run() {println("Hello, Struct Method Go Routine")s.ch <- true
}func (s *s) wait() {<-s.ch
}

COPY

我们来创建一个协程执行我们的run方法和wait方法:

func TestRoutine_ShouldSuccess_whenCreateWithStructMethod(t *testing.T) {// 定义struct变量s := &s{ch: make(chan bool),}// 创建协程go s.run()// 等待执行完成s.wait()
}}

COPY

运行截图:

file

interface{}+反射创建协程

我觉得这种方式超级复杂,但是实际业务场景中也特别有用。相当于你可以开发一个调度器,别人提交任务和任务的参数给你,你来控制怎么来调度。
看代码:
我们先定义一个调度函数,参数f是函数,args是f的参数。

func scheduleFunc(wg *sync.WaitGroup, f interface{}, args ...interface{}) {// 通过反射获取函数的定义funcVal := reflect.ValueOf(f)// 然后获取函数的参数// 使用循环把参数加入到slice中in := make([]reflect.Value, len(args))for k, param := range args {in[k] = reflect.ValueOf(param)}wg.Add(1)// 创建调用函数// 我们这儿用匿名函数包装了一下go func() {defer wg.Done() funcVal.Call(in)}()
}

COPY

然后我们定义两个任务函数, task1和task2:

func task1(a string) {fmt.Printf("Hello: %s\n", a)
}func task2(a, b string) {fmt.Printf("Hello: %s-%s\n", a, b)
}

COPY

最后我们来测试一下:

func TestRoutine_ShouldSuccess_whenCreateWithReflect(t *testing.T) {var wg sync.WaitGroup // 创建一个 WaitGroupscheduleFunc(&wg, task1, "Hello, goroutine!")scheduleFunc(&wg, task2, "Hello", "goroutine!")wg.Wait() // 等待所有 goroutine 结束}

COPY

运行截图:

file

package mainimport ("fmt""reflect"
)func worker(data []interface{}) {funcName := data[0].(string)funcArgs := data[1:] // Function or method argumentsfuncValue := reflect.ValueOf(funcMap[funcName])funcArgsValues := make([]reflect.Value, len(funcArgs))for i, arg := range funcArgs {funcArgsValues[i] = reflect.ValueOf(arg)}go funcValue.Call(funcArgsValues)
}var funcMap = map[string]interface{}{"printFunc": printFunc,"printSum":  printSum,
}func printFunc(s string) {fmt.Println(s)
}func printSum(a, b int) {fmt.Println(a + b)
}func main() {worker([]interface{}{"printFunc", "Hello, World!"})worker([]interface{}{"printSum", 1, 2})// Sleep to wait for goroutines to finishfor {}
}

COPY

设置线程和协程的数量对应关系

默认线程数量:

// 获取Go协程使用的线程数量
func TestGPROC_ShouldReturnDefaultNumer_WhenNotSetProcNumber(t *testing.T) {// 如果GOMAXPROCS()的参数为0则是获取线程数量,大于0就是设置线程数量procnum := runtime.GOMAXPROCS(0)fmt.Printf("default proc number: %d\n", procnum)
}

COPY

设置线程数量

使用代码设置线程数需要使用runtime.GOMAXPROCS设置线程数量:

// 获取Go协程使用的线程数量
func TestGPROC_ShouldReturnSpecificNumer_WhenSetProcNumber(t *testing.T) {specnum := 4// 设置线程数量为4,// 如果GOMAXPROCS()的参数为0则是获取线程数量runtime.GOMAXPROCS(specnum)fmt.Printf("set proc number: %d\n", specnum)assert.Equal(t, specnum, runtime.GOMAXPROCS(0))
}

COPY

环境变量设置

在程序启动前设置环境变量GOMAXPROCS就可以了。

export GOMAXPROCS=4

COPY

关闭协程

  1. 自行结束
  2. 手动取消

自行结束

这个和线程类似,协程执行完了就退出了,我们上面的例子都是协程执行完了自动退出。

手动取消

手动取消就需要增加控制机制了,我们来列两个手动取消的例子:

  1. context传递取消信号
  2. channel发送取消信号

我们先来定义一个后台任务,这个后台任务每个一秒钟打印一条:“Hello background task”

// 不用太关注api和语法,只需要知道每个一秒钟打印"Hello background task"
func backgroundTask(ctx context.Context) {ticker := time.NewTicker(1 * time.Second)defer ticker.Stop()for {select {case <-ctx.Done(): // 接收到取消信号,结束 goroutinereturncase <-ticker.C: // 每次 ticker 到时,打印一条消息println("Hello background task")}}
}

COPY

context传递取消信号

直接上代码:

func TestRoutine_ShouldStop_whenSendCancelWithContext(t *testing.T) {ctx, cancel := context.WithCancel(context.Background())go backgroundTask(ctx)// 让 协程 运行一段时间time.Sleep(time.Second * 5)// 发送取消信号cancel()// 给协程留一点时间处理信号time.Sleep(time.Second * 2)
}

COPY

运行截图:

file

channel发送取消信号

直接上代码:

func signaltask(ch chan bool) {for {select {// 接收到取消信号,结束协程case <-ch:return// 没有接收到取消信号,打印一条消息default:println("Hello signal task")time.Sleep(time.Second * 1)}}
}
func TestRoutine_ShouldStop_WhenSendCancelSignal(t *testing.T) {ch := make(chan bool)go signaltask(ch)// 让协程运行5秒钟time.Sleep(time.Second * 5)// 发送取消信号ch <- true// 给协程留一点时间处理信号time.Sleep(time.Second * 2)
}

COPY

运行截图:

file

搞定收工,如有错误请留言告知

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/775394.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端学习-01:Windows 安装 npm 教程

一、安装 Node.js Node.js 官方下载地址&#xff1a;点击这里点击绿色的"Download Node.js vxxxx"下载完成后双击开始安装点击 Next 接受协议&#xff0c;点击 Next 点击 Change&#xff0c;自定义安装目录&#xff0c;然后点击 Next 所有默认全部安装即可&#xff…

在 Linux CentOS 中安装 Docker Engine(Dockers 引擎)【图文详解】

官方文档&#xff1a;https://docs.docker.com/engine/install/centos/ 操作系统要求 如果我们要在 CentOS 中安装 Docker 引擎&#xff0c;那么 CentOS 操作系统需要是以下版本之一的&#xff0c;且是处于维护的 CentOS 版本&#xff1a; CentOS 7CentOS Stream 8CentOS Str…

【Web应用技术基础】CSS(4)——背景样式

第1题&#xff1a;背景颜色 .html <!DOCTYPE html> <html><head><meta charset"utf-8"><title>Hello World</title><link rel"stylesheet" href"step1/CSS/style.css"> </head><body>&…

Zookeeper的系统架构

先看一张图&#xff1a; ZooKeeper 的架构图中我们需要了解和掌握的主要有&#xff1a; 1&#xff1a; ZooKeeper分为服务器端&#xff08;Server&#xff09; 和客户端&#xff08;Client&#xff09;&#xff0c;客户端可以连接到整个ZooKeeper服务的任意服务器上&#xff…

Flink on Kubernetes (flink-operator) 部署Flink

flink on k8s 官网 https://nightlies.apache.org/flink/flink-kubernetes-operator-docs-release-1.1/docs/try-flink-kubernetes-operator/quick-start/ 我的部署脚本和官网不一样&#xff0c;有些地方官网不够详细 部署k8s集群 注意&#xff0c;按照默认配置至少有两台wo…

手机短信验证码自动转发到服务器

今天写一个自动化处理程序&#xff0c;需要验证码登录&#xff0c;怎么样把手机收到的短信自动转发到服务器接口呢&#xff1f; 利用ios手机快捷指令的功能 打开快捷指令点击中间自动化点击右上角号选择信息信息包含选取&#xff0c;输入验证码选择立即执行点击下一步按下图配…

vue + LogicFlow 实现流程图展示

vue LogicFlow 实现流程图展示 1.背景 部门主要负责低代码平台&#xff0c;配置端负责配置流程图&#xff0c;引擎端负责流程执行&#xff0c;原引擎端只负责流程执行控制以及流程历史列表展示。现在提出个新的要求&#xff0c;认为仅历史记录不直观&#xff0c;需要在展示完…

Overcooked!(并查集区间元素合并优化)

本题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网登录—专业IT笔试面试备考平台_牛客网登录—专业IT笔试面试备考平台_牛客网 题目&#xff1a; 样例&#xff1a; 输入 5 5 1 1 2 3 1 2 2 2 4 3 1 4 3 2 5 输出 YES YES NO 思路&#xff1a; 根据题意&#xff0c;这…

数据结构初阶:排序

排序的概念及其运用 排序的概念 排序 &#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性 &#xff1a;假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的记录&…

HR岗位管理:岗位职级管理体系是什么?概念、知识及方法工具梳理

岗位是组织中最具体、最小的一个基本单位。组织一般都是因事设岗&#xff0c;因而岗位是组织要求个体完成一项或多项责任而赋予个体的权力的总和。岗位是与人相对应的&#xff0c;通常一个岗位只能由一个人担任。正是因为人与岗位相匹配&#xff0c;所以企业人力资源成本管控的…

Component is not found in path “miniprogram_npm/@vant/

在微信小程序中使用vantUI库时&#xff0c;xxx.json内引入vant组件&#xff0c;报错Component is not found in path "miniprogram_npm/vant/checkbox/index &#xff0c;按报错路径查看&#xff0c;在报错目录下的包&#xff0c;文件完好存在&#xff0c;如下截图 找到n…

STM32启动方式

s在STM32F10xxx里,可以通过BOOT[1:0]引脚选择三种不同启动模式。 启动方式&#xff1a;从内部的Flash中启动、 存储器映射&#xff1a; 0x0000 0000 -----0x0800 0000 映射的内部Flash

【保姆级讲解如何Stable Diffusion本地部署】

&#x1f308;个人主页:程序员不想敲代码啊&#x1f308; &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家&#x1f3c6; &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提…

sheng的学习笔记-AI-YOLO算法,目标检测

AI目录&#xff1a;sheng的学习笔记-AI目录-CSDN博客 目录 目标定位&#xff08;Object localization&#xff09; 定义 原理图 具体做法&#xff1a; 输出向量 图片中没有检测对象的样例 损失函数 ​编辑 特征点检测&#xff08;Landmark detection&#xff09; 定义&a…

SCI一区顶刊优化算法改进:基于强化学习的神经网络算法RLNNA,你绝对没见过,非常新颖!

声明&#xff1a;文章是从本人公众号中复制而来&#xff0c;因此&#xff0c;想最新最快了解各类智能优化算法及其改进的朋友&#xff0c;可关注我的公众号&#xff1a;强盛机器学习&#xff0c;不定期会有很多免费代码分享~ 目录 神经网络优化算法NNA&#xff1a; 基于强化…

利用Python进行数据可视化Plotly与Dash的应用【第157篇—数据可视化】

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 利用Python进行数据可视化Plotly与Dash的应用 数据可视化是数据分析中至关重要的一环&…

SpringBoot集成 itextpdf 根据模板动态生成PDF

目录 需求说明前期准备Spring Boot 集成添加依赖构建工具类构建MultipartFile编辑PDF模板Java代码设置对应form的key-value 需求说明 根据合同模板&#xff0c;将动态的合同标签&#xff0c;合同方以及合同签约时间等动态的生成PDF&#xff0c;供用户下载打印。 前期准备 安…

Linux学习_进程

1.进程 概念&#xff1a;程序的一个执行实例&#xff0c;正在执行的程序等&#xff0c;担当分配系统资源&#xff08;CPU时间&#xff0c;内存&#xff09;的实体&#xff0c;进程PCB自己的代码和数据 PCB&#xff1a;进程信息被放在一个叫做进程控制块的数据结构中&#xff…

左手医生:医疗 AI 企业的云原生提效降本之路

相信这样的经历对很多人来说并不陌生&#xff1a;为了能到更好的医院治病&#xff0c;不惜路途遥远奔波到大城市&#xff1b;或者只是看个小病&#xff0c;也得排上半天长队。这些由于医疗资源分配不均导致的就医问题已是老生长谈。 云计算、人工智能、大数据等技术的发展和融…

【嵌入式——C语言】VScode编写C程序、交叉编译

【嵌入式——C语言】VScode编写C程序、交叉编译 第一步第二步第三步第四步第五步第六步第七步第八步 第一步 下载Visual Studio Code下载地址 然后直接安装就可以了。 第二步 前提是你的电脑上安装了WSL。。。 打开vscode的扩展&#xff0c;输入WSL进行安装 安装完之后在窗…