Golang并发编程-Goroutine

1Goroutine

在java/c++中我们要实现并发编程的时候,我们通常需要自己维护一个线程池,并且需要自己去包装一个又一个的任务,同时需要自己去调度线程执行任务并维护上下文切换,这一切通常会耗费程序员大量的心智。那么能不能有一种机制,程序员只需要定义很多个任务,让系统去帮助我们把这些任务分配到CPU上实现并发执行呢?

Go语言中的goroutine就是这样一种机制,goroutine的概念类似于线程,但 goroutine是由Go的运行时(runtime)调度和管理的。Go程序会智能地将 goroutine 中的任务合理地分配给每个CPU。Go语言之所以被称为现代化的编程语言,就是因为它在语言层面已经内置了调度和上下文切换的机制。

在Go语言编程中你不需要去自己写进程、线程、协程,你的技能包里只有一个技能–goroutine,当你需要让某个任务并发执行的时候,你只需要把这个任务包装成一个函数,开启一个goroutine去执行这个函数就可以了,就是这么简单粗暴。

使用goroutine

Go语言中使用goroutine非常简单,只需要在调用函数的时候在前面加上go关键字,就可以为一个函数创建一个goroutine。

一个goroutine必定对应一个函数,可以创建多个goroutine去执行相同的函数。

启动单个goroutine

启动goroutine的方式非常简单,只需要在调用的函数(普通函数和匿名函数)前面加上一个go关键字。

举个例子如下:

func hello() {fmt.Println("Hello Goroutine!")
}
func main() {hello()fmt.Println("main goroutine done!")
}

这个示例中hello函数和下面的语句是串行的,执行的结果是打印完Hello Goroutine!后打印main goroutine done!。

接下来我们在调用hello函数前面加上关键字go,也就是启动一个goroutine去执行hello这个函数。

func main() {go hello() // 启动另外一个goroutine去执行hello函数fmt.Println("main goroutine done!")
}

这一次的执行结果只打印了main goroutine done!,并没有打印Hello Goroutine!。为什么呢?

在程序启动时,Go程序就会为main()函数创建一个默认的goroutine。

当main()函数返回的时候该goroutine就结束了,所有在main()函数中启动的goroutine会一同结束,main函数所在的goroutine就像是权利的游戏中的夜王,其他的goroutine都是异鬼,夜王一死它转化的那些异鬼也就全部GG了。

所以我们要想办法让main函数等一等hello函数,最简单粗暴的方式就是time.Sleep了。

func main() {go hello() // 启动另外一个goroutine去执行hello函数fmt.Println("main goroutine done!")time.Sleep(time.Second)
}

执行上面的代码你会发现,这一次先打印main goroutine done!,然后紧接着打印Hello Goroutine!。

首先为什么会先打印main goroutine done!是因为我们在创建新的goroutine的时候需要花费一些时间,而此时main函数所在的goroutine是继续执行的。

启动多个goroutine

在Go语言中实现并发就是这样简单,我们还可以启动多个goroutine。让我们再来一个例子: (这里使用了sync.WaitGroup来实现goroutine的同步)

var wg sync.WaitGroupfunc hello(i int) {defer wg.Done() // goroutine结束就登记-1fmt.Println("Hello Goroutine!", i)
}
func main() {for i := 0; i < 10; i++ {wg.Add(1) // 启动一个goroutine就登记+1go hello(i)}wg.Wait() // 等待所有登记的goroutine都结束
}

多次执行上面的代码,会发现每次打印的数字的顺序都不一致。这是因为10个goroutine是并发执行的,而goroutine的调度是随机的。

注意
  • 如果主协程退出了,其他任务还执行吗(运行下面的代码测试一下吧)
package mainimport ("fmt""time"
)func main() {// 合起来写go func() {i := 0for {i++fmt.Printf("new goroutine: i = %d\n", i)time.Sleep(time.Second)}}()i := 0for {i++fmt.Printf("main goroutine: i = %d\n", i)time.Sleep(time.Second)if i == 2 {break}}
}

1.1.1. goroutine与线程

可增长的栈

OS线程(操作系统线程)一般都有固定的栈内存(通常为2MB),一个goroutine的栈在其生命周期开始时只有很小的栈(典型情况下2KB),goroutine的栈不是固定的,他可以按需增大和缩小,goroutine的栈大小限制可以达到1GB,虽然极少会用到这个大。所以在Go语言中一次创建十万左右的goroutine也是可以的。

goroutine调度

GPM是Go语言运行时(runtime)层面的实现,是go语言自己实现的一套调度系统。区别于操作系统调度OS线程。

  • 1.G很好理解,就是个goroutine的,里面除了存放本goroutine信息外 还有与所在P的绑定等信息。
  • 2.P管理着一组goroutine队列,P里面会存储当前goroutine运行的上下文环境(函数指针,堆栈地址及地址边界),P会对自己管理的goroutine队列做一些调度(比如把占用CPU时间较长的goroutine暂停、运行后续的goroutine等等)当自己的队列消费完了就去全局队列里取,如果全局队列里也消费完了会去其他P的队列里抢任务。
  • 3.M(machine)是Go运行时(runtime)对操作系统内核线程的虚拟, M与内核线程一般是一一映射的关系, 一个groutine最终是要放到M上执行的;

P与M一般也是一一对应的。他们关系是: P管理着一组G挂载在M上运行。当一个G长久阻塞在一个M上时,runtime会新建一个M,阻塞G所在的P会把其他的G 挂载在新建的M上。当旧的G阻塞完成或者认为其已经死掉时 回收旧的M。

P的个数是通过runtime.GOMAXPROCS设定(最大256),Go1.5版本之后默认为物理线程数。 在并发量大的时候会增加一些P和M,但不会太多,切换太频繁的话得不偿失。

单从线程调度讲,Go语言相比起其他语言的优势在于OS线程是由OS内核来调度的,goroutine则是由Go运行时(runtime)自己的调度器调度的,这个调度器使用一个称为m:n调度的技术(复用/调度m个goroutine到n个OS线程)。 其一大特点是goroutine的调度是在用户态下完成的, 不涉及内核态与用户态之间的频繁切换,包括内存的分配与释放,都是在用户态维护着一块大的内存池, 不直接调用系统的malloc函数(除非内存池需要改变),成本比调度OS线程低很多。 另一方面充分利用了多核的硬件资源,近似的把若干goroutine均分在物理线程上, 再加上本身goroutine的超轻量,以上种种保证了go调度方面的性能。

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

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

相关文章

jenkins参数化构建在UI中定义脚本中使用

先看配置&#xff1a; 流水线脚本&#xff1a; pipeline {agent {//label "${server}"label "${28}"}stages {stage(Hello) {steps {echo "--------------------------"// 只有这个可以输出变量echo "${character_argument}"echo &q…

网络通信---TCP协议1

今日内容 三次握手: 指建立tcp连接时&#xff0c;需要客户端和服务端总共发送三次报文确认连接。 四次挥手&#xff1a; 断开一个tcp连接&#xff0c;需要客户端和服务端发送四个报文以确认断开。 编程模型 TCP报文 客户端 服务端

redis,电脑缓存

由于目前互联网巨大的访问量&#xff0c;在生产环境中常常需要redis结合mysql来用&#xff0c;我们可以将redis当作mysql的缓存&#xff0c;应用(app)所有读的操作都负载到redis上&#xff0c;因为redis够快&#xff0c;如果直接从mysql上读会对它造成巨大的压力&#xff0c;之…

E21.“详解函数递归”文中的趣味练习的答案

详解函数递归原文 高考标答&#xff1a; 思路&#xff1a; 代码实现&#xff1a; //这里取αn1 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> double function(double n) {if (1 n){return 2;}else{return 1 1.0 / (function(n - 1));} } int main() {int n …

C# 多线程Paralle使用

在C#中&#xff0c;Parallel 类是 System.Threading.Tasks 命名空间下的一个静态类&#xff0c;它提供了并行执行循环和操作的简便方法。通过使用 Parallel 类&#xff0c;可以简化多线程编程&#xff0c;提高应用程序执行并行任务的能力。Parallel 类主要用于并行执行 for 和 …

小程序跳转防止页面栈卡死

小程序中页面栈为10个&#xff0c;超过10个之后小程序则无法进行跳转&#xff1b; 解决方法&#xff1a;进行页面栈判断有就返回&#xff0c;没有但没10个就navigateTo否则redirectTo&#xff1b; /*** 跳转定制历史*/ goCustomizeHistory() {let index getCurrentPages().f…

大自然的传奇——龙宫

我们1小时后就到了龙宫&#xff0c;导游给我们买票去了&#xff0c;让我们去观景台上&#xff0c;看游客中心后面“龙”字草书。龙字田采用两种农作物套种&#xff0c;按季节区分&#xff0c;春天由油菜花和蚕豆进行套种&#xff0c;秋天由黑糯米和一般水稻进行套种。我们来的夏…

json数据格式 继续学习

1.定义 轻量级的数据交互格式&#xff0c;可以按照json数据格式去组织和封装数据。 本质是一个带有特定格式的字符串。 2.功能 负责不同编程语言中的数据传递和交互。 3.json数据格式转化 """ 演示json数据和python字典之间的转换 """ impor…

【NPU 系列专栏 2.4 -- 高速互连 NVLink 详细介绍】

请阅读【嵌入式及芯片开发学必备专栏】 文章目录 NVLink 简介NVLink 主要特点NVLink 应用场景NVLink 工作原理NVLink 实例介绍DL 中使用 NVLinkHPC 中使用 NVLinkSummaryNVLink 简介 NVLink 是 NVIDIA 开发的一种高速互连技术,旨在提升 GPU 与 GPU 之间以及 GPU 与 CPU 之间的…

Spring Task详解

文章目录 一、开启定时任务二、cron表达式 Spring Task是Spring框架提供的任务调度工具&#xff0c;可以按照约定的时间自动执行某个代码逻辑 一、开启定时任务 Spring Boot 默认在无任何第三方依赖的情况下使用 spring-context 模块下提供的定时任务工具 Spring Task。我们只…

帕金森病(PD)诊断:三种基于语音的深度学习方法

帕金森病&#xff08;Parkinson’s disease, PD&#xff09;是世界上第二大流行的神经退行性疾病&#xff0c;全球影响着超过1000万人&#xff0c;仅次于阿尔茨海默症。人们通常在65岁左右被诊断出患有此病。PD的一些症状包括震颤、肌肉僵硬和运动迟缓。这些症状往往出现在较晚…

MySQL之视图和索引实战

1.新建数据库 mysql> create database myudb5_indexstu; Query OK, 1 row affected (0.01 sec) mysql> use myudb5_indexstu; Database changed 2.新建表 1.学生表student&#xff0c;定义主键&#xff0c;姓名不能重名&#xff0c;性别只能输入男或女&#xff0c;所在…

Linux-Centos-改密码(单用户登陆)

笔记一&#xff1a; centos7单用户修改root密码 在CentOS 7中&#xff0c;如果您是唯一的用户或者您确信其他用户不会登录&#xff0c;您可以按照以下步骤来修改root密码&#xff1a; 1.重启系统。 2.启动时出现引导界面时&#xff0c;按任意键进入GRUB菜单。 3.选择要启动的内…

在线教育数仓项目(数据采集部分1)

文章目录 数据仓库概念项目需求及架构设计项目需求分析系统数据流程设计框架版本选型集群规模估算集群资源规划设计 数据生成模块目标数据页面事件曝光启动播放错误 数据埋点主流埋点方式&#xff08;了解&#xff09;埋点数据上报时机埋点数据日志结构 服务器和JDK准备服务器准…

缝缝补补的生活

工作之后荒废了生活&#xff0c;工作成了主旋律&#xff0c;没有兴趣、没有爱好、没有增长知识、没有丰富阅历浑浑噩噩的度过了工作后的近几年时间。有志之人立长志无志之人常立志&#xff0c;经常说重新开始&#xff0c;从头来过但是我们的生活从出生就是一直向前的&#xff0…

白鲸开源CEO郭炜荣获「2024中国数智化转型升级先锋人物」称号

2024年7月24日&#xff0c;由数据猿主办&#xff0c;IDC协办&#xff0c;新华社中国经济信息社、上海大数据联盟、上海市数商协会、上海超级计算中心作为支持单位&#xff0c;举办“数智新质力拓未来 2024企业数智化转型升级发展论坛——暨AI大模型趋势论坛”数据猿“年中特别策…

c语言(7.27)

今天学习了利用索引遍历第二种格式的二维数组 #include<stdio.h> int main() {//定义三个一维数组int arr1[3] { 1,2,3 };int arr2[5] { 1,2,3,4,5 };int arr3[9] { 1,2,3,4,5,6,7,8,9 };//分别算出每个一维数组的长度int len1 sizeof(arr1) / sizeof(int);int len2 …

探索 Electron:构建用户友好的登录页面流程

Electron是一个开源的桌面应用程序开发框架&#xff0c;它允许开发者使用Web技术&#xff08;如 HTML、CSS 和 JavaScript&#xff09;构建跨平台的桌面应用程序&#xff0c;它的出现极大地简化了桌面应用程序的开发流程&#xff0c;让更多的开发者能够利用已有的 Web 开发技能…

指针的面试题

这里写目录标题 判断链表中是否有环描述代码检测链表中是否存在环链表中存在环想检测链表中是否存在环&#xff0c;而不需要找到环的入口 判断链表中是否有环 题目 描述 判断给定的链表中是否有环。如果有环则返回true&#xff0c;否则返回false。 数据范围&#xff1a;链表…

精准制导:选择适合的Memcached缓存策略

&#x1f3af; 精准制导&#xff1a;选择适合的Memcached缓存策略 Memcached是一个广泛使用的高性能分布式内存缓存系统&#xff0c;它的缓存策略对于优化应用性能至关重要。正确的缓存策略可以显著减少数据库访问次数&#xff0c;降低延迟&#xff0c;提高响应速度。本文将深…