逐步学习Go-协程goroutine

news2024/10/1 23:41:45

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 := <-ch
    assert.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 := <-ch
    assert.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 // 创建一个 WaitGroup

    scheduleFunc(&wg, task1, "Hello, goroutine!")
    scheduleFunc(&wg, task2, "Hello", "goroutine!")

    wg.Wait() // 等待所有 goroutine 结束

}

COPY

运行截图:

file

package main

import (
    "fmt"
    "reflect"
)

func worker(data []interface{}) {
    funcName := data[0].(string)
    funcArgs := data[1:] // Function or method arguments

    funcValue := 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 finish
    for {}
}

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(): // 接收到取消信号,结束 goroutine
            return
        case <-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.coloradmin.cn/o/1549726.html

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

相关文章

jMeter学习

一. JMeter介绍 1. 什么是JMeter&#xff1f; Apache JMeter™ 应用程序是开源软件&#xff0c;一个 100% 纯 Java 应用程序&#xff0c;旨在加载测试功能行为和测量性能 。它最初是为测试 Web 应用程序而设计的&#xff0c;但后来扩展到其他测试功能。 2. JMeter能做啥&#x…

RabbitMQ(简单模式)

2种远程服务调用 1openFeign&#xff1a; 优点&#xff1a;能拿到被调用的微服务返回的数据&#xff0c;系统系耦度高&#xff0c;系统稳定。 缺点&#xff1a;同步调用&#xff0c;如果有很多服务需要被调用&#xff0c;耗时长。 MQ,消息队列&#xff0c;RabbitMQ是消息we…

数据可视化-ECharts Html项目实战(8)

在之前的文章中&#xff0c;我们学习了如何设置散点图涟漪效果与仪表盘动态指针效果。想了解的朋友可以查看这篇文章。同时&#xff0c;希望我的文章能帮助到你&#xff0c;如果觉得我的文章写的不错&#xff0c;请留下你宝贵的点赞&#xff0c;谢谢 今天的文章&#xff0c;会…

标定系列——预备知识-OpenCV中实现Rodrigues变换的函数(二)

标定系列——预备知识-OpenCV中实现Rodrigues变换的函数&#xff08;二&#xff09; 说明记录 说明 简单介绍罗德里格斯变换以及OpenCV中的实现函数 记录

Machine Learning机器学习之向量机(Support Vector Machine,SVM)

目录 前言 算法提出背景&#xff1a; 核心思想&#xff1a; 原理&#xff1a; 应用领域&#xff1a; 一、支持向量机分类&#xff08;主要变体&#xff09; 二、构建常见的支持向量机模型 基于Python 中的 Scikit-learn 库构建线性支持向量机&#xff08;SVM&#xff09; 三、向…

蓝桥杯刷题之路径之谜

题目来源 路径之谜 不愧是国赛的题目 题意 题目中会给你两个数组&#xff0c;我这里是分别用row和col来表示 每走一步&#xff0c;往左边和上边射一箭&#xff0c;走到终点的时候row数组和col数组中的值必须全部等于0这个注意哈&#xff0c;看题目看了半天&#xff0c;因为…

【GPU系列】选择最适合的 CUDA 版本以提高系统性能

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

NVIDIA 发布 Project GR00T 人形机器人基础模型和 Isaac 机器人平台重大更新

系列文章目录 前言 Isaac 机器人平台现可为开发者提供全新的机器人训练仿真器、Jetson Thor 机器人计算机、生成式 AI 基础模型和由 CUDA 加速的感知和操作库。 Project GR00T 是一种多模态人形机器人通用基础模型&#xff0c;作为机器人的大脑&#xff0c;使它们能够学习技能…

Android客户端自动化UI自动化airtest从0到1搭建macos+demo演示

iOS客户端自动化UI自动化airtest从0到1搭建macosdemo演示-CSDN博客 一、基础环境 1. 安装jdk 选择jdk8 如果下载高版本 可能不匹配会失败 下载.dmg文件 苹果电脑 &#xff5c; macOS &#xff5c; jdk1.8 &#xff5c; 环境变量配置_jdk1.8 mac-CSDN博客 Java Downloads …

【Java程序设计】【C00390】基于(JavaWeb)Springboot的校园疫情防控管理系统(有论文)

基于&#xff08;JavaWeb&#xff09;Springboot的校园疫情防控管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开…

前端Web移动端学习day05

移动 Web 第五天 响应式布局方案 媒体查询Bootstrap框架 响应式网页指的是一套代码适配多端&#xff0c;一套代码适配各种大小的屏幕。 共有两种方案可以实现响应式网页&#xff0c;一种是媒体查询&#xff0c;另一种是使用bootstrap框架。 01-媒体查询 基本写法 max-wid…

vue3+Vite+TS项目,配置ESlint和Prettier

创建vue3项目 实操过的有两种方式 1.vue脚手架2.vite&#xff08;推荐&#xff0c;也是尤大大团队研发&#xff09; 具体怎么新建一个vue3项目就不多讲了&#xff0c;可以按照官方文档来 创建后的文件目录长这样 多提一句&#xff0c;vite也会随着时间不断迭代&#xff0c;后…

【C++入门】 初见,单推,与C++的第一次约会

关注小庄 顿顿解馋(ᕑᗢᓫ∗)˒ 引言&#xff1a;本篇博客我们开始与C的第一次约会&#xff0c;C是兼容c的&#xff0c;本篇博客我们将了解到C关键字有哪些&#xff0c;C命名空间&#xff0c;C输入与输出和缺省参数的内容&#xff0c;请放心食用 ~ 文章目录 一 &#x1f3e0; C…

github配置ssh

生成公钥 在电脑用户的目录下打开终端执行 ssh-keygen -t rsa: 执行完不要关 配置文件 看看用户的目录里 .ssh 目录&#xff1a; Host github.comHostname ssh.github.comPort 443配置公钥 复制 id_rsa.pub 文件里的内容 粘贴到 github上 连接密钥 回到刚才的终端…

【42 可视化大屏 | 某瓣电影Top250数据分析可视化大屏】

文章目录 &#x1f3f3;️‍&#x1f308; 1 普版大屏&#x1f3f3;️‍&#x1f308;2 Flask版大屏&#x1f3f3;️‍&#x1f308;3 FlaskMysql版大屏&#x1f3f3;️‍&#x1f308; 4. 可视化项目源码数据 大家好&#xff0c;我是 &#x1f449;【Python当打之年(点击跳转)…

学透Spring Boot — 创建一个简单Web应用

从今天开始&#xff0c;我们将开始学习一个新的系列&#xff0c;那就是在项目中用得非常广泛的一个框架 —— Spring Boot&#xff0c;我们会循序渐进地介绍 Spring Boot 的方方面面&#xff0c;包括理论和实战&#xff0c;也会介绍和Spring Boot一些热点面试题。 概论 本文是…

基于Rflysim平台的无人机拦截三维比例导引算法仿真

【后厂村路钢铁侠出品】 一、Rflysim简介 RflySim是一套专为科研和教育打造的Pixhawk /PX4 和MATLAB/Simulink生态系统或工具链&#xff0c;采用基于模型设计&#xff08;Model-Based Design&#xff0c; MBD&#xff09;的思想&#xff0c;可用于无人系统的控制和安全测试。…

社交革命:Facebook如何改变我们的生活

引言 在数字化时代的浪潮中&#xff0c;社交媒体平台成为了人们日常生活不可或缺的一部分&#xff0c;而Facebook作为其中的巨头&#xff0c;已经深刻地改变了我们的生活方式和社交模式。本文将探讨Facebook在社交领域的影响力&#xff0c;以及它是如何改变了我们的生活。 1. …

SQLite数据库浏览器sqlite-web

什么是 sqlite-web &#xff1f; sqlite-web是一个用 Python 编写的基于 Web 的 SQLite 数据库浏览器。 软件特点&#xff1a; 可与您现有的 SQLite 数据库配合使用&#xff0c;也可用于创建新数据库。添加或删除&#xff1a; 表格列&#xff08;支持旧版本的 SQLite&#xff…

春秋云境CVE-2023-1313

简介 cockpit在2.4.1版本之前存在任意文件上传漏洞PS&#xff1a;通过在浏览器中打开/install来运行安装 正文 来到靶场&#xff0c;首先进行弱口令爆破&#xff0c;发现没用&#xff0c;那么只好老老实实的看靶场提示 先来访问/install 访问后就可以进行登录了&#xff0c…