Go开发指南- Goroutine

目录:
(1)Go开发指南-Hello World
(2)Go开发指南-Gin与Web开发
(3)Go开发指南-Goroutine

Goroutine

在java中我们要实现并发编程的时候,通常要自己维护一个线程池,并且需要去包装任务、调度任务和维护上下文切换。这个过程需要消耗大量的精力。

Go语言中有一种机制,可以让系统自动把任务分配到CPU上实现并发执行,而不需要人工去管理这些任务。这就是goroutine。

Goroutine类似于线程,但比线程更轻量,可以称之为协程。它由运行时(runtime)调度和管理,自动进行上下文切换,这也是go被称之为现代化编程语言的原因。

使用Goroutine

Go中使用goroutine非常简单,只需要在调用函数的时候加上go关键字。一个goroutine必定对应一个函数,可以创建多个goroutine去执行相同的函数。

下面是一个示例:


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

这个例子中hello函数和主函数中的打印信息是串行的。

先将hello函数改成goroutine的:

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

再次执行会发现只打印main goroutine done!。这是因为main函数本身是在一个默认的goroutine中执行的,当main函数结束时,此goroutine运行结束,在main函数中启动的其他goroutine也会随之退出。

修改main函数:

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

此时再次执行就会再次打印两条信息了。

启动多个goroutine

在go中,可以同时启动多个goroutine:

package mainimport ("fmt""sync"
)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都结束
}

这里使用sync.WaitGroup来实现goroutine的同步。

执行代码,会发现10个协程并发打印信息,并且顺序是随机的(goroutine调度是随机的)。

Goroutine与线程

一个goroutine的栈内存在生命周期开始时只有2KB,但可以按需增大和缩小,最大可达到1GB。

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

事实上,GPM并不是官方术语,而是开发者用来概括go的并发模型的三大核心组件的:Goroutine、Processor、Machine。

Goroutine拥有自己的栈和上下文,其切换由运行时调度器管理,不依赖于操作系统的线程管理,因此比传统线程更轻量。

Processor表示逻辑处理器,管理着goroutine的队列,并负责调度goroutine到可用的machine上执行。P的数量决定了可以同时运行多个goroutine,可通过runtime.GOMAXPROCS设置(最大256),默认与CPU核数相等。

Machine表示内核线程(或系统线程),是在操作系统层面上执行任务的线程。Go运行时会将goroutine绑定到M上运行。换句话说,M负责实际执行P中的goroutine。当M在运行goroutine时,可以根据情况继续运行该goroutine,也可以将其切换出去以运行其他goroutine。

GMP示意图:
在这里插入图片描述
从线程调度讲,Go语言相比其他语言的优势在于goroutine是由go运行时自己调度的。这个调度器使用一个被称为m:n调度的技术,即将m个goroutine调度到n个OS线程上。其一大特点是goroutine的调度是在用户态下完成的,不涉及内核态与用户态之间频繁切换,包括内存的分配与释放,成本比调度OS线程低很多。

channel

很多场景下并发地协程之间是需要互相通信的,比如经典的并发同步问题:用两个协程交替打印奇数和偶数,这时候就要在两个协程之间互相通信,来保证打印的顺序。 Go通过channel实现协程间的通信。

共享内存也可以进行数据交换,但是共享内存容易出现并发安全问题,为了保证数据的准确性,需要使用互斥量对内存进行加锁,造成额外的性能消耗。

Channel 是有类型的管道,遵循先进先出的规则,保证数据的顺序。Channel 采用关键字chan 加上类型做声明,赋值取值采用符号<-

Channel是引用类型,默认为nil。

var ch chan int 
fmt.Println(ch)  // 输出为<nil>

声明的通道需要使用使用make函数初始化之后才能使用:

ch := make(chan int)

channel操作

channel有发送,接收和关闭三种操作。如下所示:

func test(ch chan<- int) {ch <- 10close(ch)
}func main() {ch := make(chan int)go test(ch)fmt.Println(<-ch)
}

channel是有方向的,chan 是一个双向通道,既可以发送数据,也可以从中接收数据。chan<- 是一个单向通道,只能往其中发送数据。<-chan表示这是一个单向通道,只能往外取数据。

关闭通道并不是必须的,而是可以让系统自动垃圾回收。需要关闭通道的情况:明确知道没有更多的数据会被发送到通道时,可以关闭通道。关闭通道可以让接收方在读取所有数据后,通过检测到通道的关闭信号,安全地停止接收数据。

关闭后的通道有以下特点:

  • 对一个关闭的通道发送数据会导致panic。
  • 对一个关闭的通道接收数据会正常获取,如果通道里没有值了,会获取到对应类型的零值。
  • 重复关闭通道会导致panic。

一般只有发送方才会主动关闭通道。

无缓冲channel和缓冲channel

无缓冲channel

无缓冲channel又称为阻塞channel,如下所示:

func main() {ch := make(chan int)ch <- 10fmt.Println("send success")
}

这段代码可以编译通过,但是执行会报错:all goroutines are asleep - deadlock!。原因是这是一个无缓冲channel,只有数据发送方,但是没有接收方,代码会在ch <- 10 阻塞住,形成死锁。

添加一个接收方解决死锁问题:

func recv(ch chan int) {ret := <-chfmt.Println("recv success", ret)
}func main() {ch := make(chan int)go recv(ch)ch <- 10fmt.Println("send success")
}

无缓冲通道上的发送操作会阻塞,直到另一个goroutine在该通道上执行接收操作,这时才能发送成功,两个goroutine将继续执行。相反,如果接收操作先执行,接收方的goroutine将阻塞,直到另一个goroutine在该通道上发送一个值。这与阻塞队列的工作方式是类似的。

使用无缓冲通道进行通信将导致发送和接收的goroutine同步化。因此,无缓冲通道也被称为同步通道。

有缓冲channel

下面创建一个有缓冲的channel:

func main() {ch := make(chan int, 1)ch <- 10fmt.Println("send success")
}

只要channel的容量大于零,则就是一个有缓冲的通道。

遍历通道

func main() {ch1 := make(chan int)ch2 := make(chan int)go func() {for i := 0; i < 100; i++ {ch1 <- i}close(ch1)}()  // 匿名函数go func() {for {i, ok := <-ch1 // if ok = false, it means the channel is closedif !ok {break}ch2 <- i * i}close(ch2)}() // 匿名函数for i := range ch2 { // the for struct will exits when channel is closedfmt.Println(i)}
}

select

select是Go中的关键字,可以同时响应多个channel的操作。其使用类似于switch语句,有一系列case
分支和一个默认的分支。每个case会对应一个通道的通信过程。select会一直等待,直到某个case的通信操作完成时,就会执行case分支对应的语句。如下所示:

func test1(ch chan string) {time.Sleep(time.Second * 5)ch <- "test1"
}func test2(ch chan string) {time.Sleep(time.Second * 2)ch <- "test2"
}func main() {output1 := make(chan string)output2 := make(chan string)go test1(output1)go test2(output2)select {case s1 := <-output1:fmt.Println("s1=", s1)case s2 := <-output2:fmt.Println("s2=", s2)}
}

在这个例子中,只要任何一个通道的通信完成,就会执行对应的case分支。如果多个channel同时ready,会随机选择一个执行。

参考资料

[1]. https://go.dev/doc/tutorial/
[2].https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/channel.html

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

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

相关文章

解决failed to execute PosixPath(‘dot‘) 或者GraphViz‘s executables not found

在网上找了很多方法都没解决&#xff0c;所以写一篇文章帮助和我遇到同样问题的人 解决方法&#xff1a; 因为python解释器会解释转移字符&#xff0c;因此在环境变量中把\bin换成\\bin即可 解决过程&#xff1a; 系统&#xff1a;win10 已安装pip install graphviz&#xff0…

力扣 LeetCode 541. 反转字符串II(Day4:字符串)

解题思路&#xff1a; i可以成段成段的跳&#xff0c;而不是简单的i class Solution {public String reverseStr(String s, int k) {char[] ch s.toCharArray();// 1. 每隔 2k 个字符的前 k 个字符进行反转for (int i 0; i < ch.length; i 2 * k) {// 2. 剩余字符小于 …

官方压测工具memtier-benchmark压测redis

1 概述 memtier_benchmark是一种高吞吐量的性能基准测试工具&#xff0c;主要用于Redis和Memcached。它是 Redis官方开发团队开发的&#xff0c;旨在生成各种流量模式&#xff0c;以便测试和优化以上两个数据库的性能。 memtier_benchmark的一些关键特点如下&#xff1a; 多…

用 Python 从零开始创建神经网络(三):添加层级(Adding Layers)

添加层级&#xff08;Adding Layers&#xff09; 引言1. Training Data2. Dense Layer Class 引言 我们构建的神经网络变得越来越受人尊敬&#xff0c;但目前我们只有一层。当神经网络具有两层或更多隐藏层时&#xff0c;它们变成了“深度”网络。目前我们只有一层&#xff0c…

MFC工控项目实例三十实现一个简单的流程

启动按钮夹紧 密闭&#xff0c;时间0到平衡 进气&#xff0c;时间1到进气关&#xff0c;时间2到平衡关 检测&#xff0c;时间3到平衡 排气&#xff0c;时间4到夹紧开、密闭开、排气关。 相关代码 void CSEAL_PRESSUREDlg::OnTimer_2(UINT nIDEvent_2) {// if (nIDEvent_21 &am…

Java I/O(输入/输出)——针对实习面试

目录 Java I/O&#xff08;输入/输出&#xff09;什么是Java I/O流&#xff1f;字节流和字符流有什么区别&#xff1f;什么是缓冲流&#xff1f;为什么要使用缓冲流&#xff1f;Java I/O中的设计模式有哪些&#xff1f;什么是BIO&#xff1f;什么是NIO&#xff1f;什么是AIO&am…

Exploring Defeasible Reasoning in Large Language Models: A Chain-of-Thought A

文章目录 题目摘要简介准备工作数据集生成方法实验结论 题目 探索大型语言模型中的可废止推理&#xff1a;思路链 论文地址&#xff1a;http://collegepublications.co.uk/downloads/LNGAI00004.pdf#page136 摘要 许多大型语言模型 (LLM) 经过大量高质量数据语料库的训练&…

数据结构--数组

一.线性和非线性 线性&#xff1a;除首尾外只有一个唯一的前驱和后继。eg&#xff1a;数组&#xff0c;链表等。 非线性&#xff1a;不是线性的就是非线性。 二.数组是什么&#xff1f; 数组是一个固定长度的存储相同数据类型的数据结构&#xff0c;数组中的元素被存储在一…

MySQL技巧之跨服务器数据查询:基础篇-更新语句如何写

MySQL技巧之跨服务器数据查询&#xff1a;基础篇-更新语句如何写 上一篇已经描述&#xff1a;借用微软的SQL Server ODBC 即可实现MySQL跨服务器间的数据查询。 而且还介绍了如何获得一个在MS SQL Server 可以连接指定实例的MySQL数据库的连接名: MY_ODBC_MYSQL 以及用同样的…

Unity教程(十八)战斗系统 攻击逻辑

Unity开发2D类银河恶魔城游戏学习笔记 Unity教程&#xff08;零&#xff09;Unity和VS的使用相关内容 Unity教程&#xff08;一&#xff09;开始学习状态机 Unity教程&#xff08;二&#xff09;角色移动的实现 Unity教程&#xff08;三&#xff09;角色跳跃的实现 Unity教程&…

前端学习八股资料CSS(二)

更多详情&#xff1a;爱米的前端小笔记&#xff0c;更多前端内容&#xff0c;等你来看&#xff01;这些都是利用下班时间整理的&#xff0c;整理不易&#xff0c;大家多多&#x1f44d;&#x1f49b;➕&#x1f914;哦&#xff01;你们的支持才是我不断更新的动力&#xff01;找…

云计算研究实训室建设方案

一、引言 随着云计算技术的迅速发展和广泛应用&#xff0c;职业院校面临着培养云计算领域专业人才的迫切需求。本方案旨在构建一个先进的云计算研究实训室&#xff0c;为学生提供一个集理论学习、实践操作、技术研发与创新于一体的综合性学习平台&#xff0c;以促进云计算技术…

通过Python 调整Excel行高、列宽

在Excel中&#xff0c;默认的行高和列宽可能不足以完全显示某些单元格中的内容&#xff0c;特别是当内容较长时。通过调整行高和列宽&#xff0c;可以确保所有数据都能完整显示&#xff0c;避免内容被截断。合理的行高和列宽可以使表格看起来更加整洁和专业&#xff0c;尤其是在…

【代码审计】常见漏洞专项审计-业务逻辑漏洞审计

❤️博客主页&#xff1a; iknow181 &#x1f525;系列专栏&#xff1a; 网络安全、 Python、JavaSE、JavaWeb、CCNP &#x1f389;欢迎大家点赞&#x1f44d;收藏⭐评论✍ 0x01 漏洞介绍 1、 原理 业务逻辑漏洞是一类特殊的安全漏洞&#xff0c;业务逻辑漏洞属于设计漏洞而非实…

Wordpress常用配置,包括看板娘跨域等

一个Wordpress的博客已经搭建完成了&#xff0c;那么为了让它看起来更有人间烟火气一点&#xff0c;有一些常用的初始配置&#xff0c;这里整理一下。 修改页脚 页脚这里默认会显示Powered by Wordpress&#xff0c;还有一个原因是这里要加上备案信息。在主题里找到页脚&…

数据库范式、MySQL 架构、算法与树的深入解析

一、数据库范式 在数据库设计中&#xff0c;范式是一系列规则&#xff0c;用于确保数据的组织和存储具有良好的结构、完整性以及最小化的数据冗余。如果不遵循范式设计&#xff0c;数据可能会以平铺式罗列&#xff0c;仅使用冒号、分号等简单分隔。这种方式存在诸多弊端&#…

Taro React-Native IOS 打包发布

http网络请求不到 配置 fix react-native facebook::flipper::SocketCertificateProvider‘ (aka ‘int‘) is not a function or func_rn运行debug提示flipper-CSDN博客 Xcode 15&#xff08;iOS17&#xff09;编译适配报错_no template named function in namespace std-CS…

基于yolov8、yolov5的车型检测识别系统(含UI界面、训练好的模型、Python代码、数据集)

摘要&#xff1a;车型识别在交通管理、智能监控和车辆管理中起着至关重要的作用&#xff0c;不仅能帮助相关部门快速识别车辆类型&#xff0c;还为自动化交通监控提供了可靠的数据支撑。本文介绍了一款基于YOLOv8、YOLOv5等深度学习框架的车型识别模型&#xff0c;该模型使用了…

从python源码到可自动更新软件

相关阅读 标题链接如何打包python程序为exebczl【auto-py-to-exe 可视化打包python到exe】51CTO ZATA 1. python源码 打包时需要特别注意的源码编写规范 除了基本的 Python 编码规范之外,在准备程序进行打包时,还需要特别注意以下几点: 1.1 依赖管理 确保 requirements.t…

javaWeb小白项目--学生宿舍管理系统

目录 一、检查并关闭占用端口的进程 二、修改 Tomcat 的端口配置 三、重新启动 Tomcat 一、javaw.exe的作用 二、结束javaw.exe任务的影响 三、如何判断是否可以结束 结尾&#xff1a; 这个错误提示表明在本地启动 Tomcat v9.0 服务器时遇到了问题&#xff0c;原因是所需…