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
相关文章:

Go开发指南- Goroutine
目录: (1)Go开发指南-Hello World (2)Go开发指南-Gin与Web开发 (3)Go开发指南-Goroutine Goroutine 在java中我们要实现并发编程的时候,通常要自己维护一个线程池,并且需要去包装任务、调度任务和维护上下文切换。这个过程需要消耗大量的精…...

Dubbo 3.x源码(24)—Dubbo服务引用源码(7)接口级服务发现订阅refreshInterfaceInvoker
基于Dubbo 3.1,详细介绍了Dubbo服务的发布与引用的源码。 此前我们学习了Dubbo3.1版本的MigrationRuleHandler这个处理器,它用于通过动态更改规则来控制迁移行为。MigrationRuleListener的onrefer方法是Dubbo2.x 接口级服务发现与Dubbo3.x应用级服务发现…...

高级java每日一道面试题-2024年11月04日-Redis篇-Redis如何做内存优化?
如果有遗漏,评论区告诉我进行补充 面试官: Redis如何做内存优化? 我回答: 在Java高级面试中,关于Redis如何做内存优化的问题,可以从以下几个方面进行详细解答: 一、Redis内存优化概述 Redis内存优化主要是指通过一系列策略和技术&#…...

数据结构 -二叉搜索树
一.什么是二叉搜索树 树插入删除方便比线性数组 二.二叉搜索树的查找操作 尾递归可以用循环递归 三.二叉树的插入操作 35要挂在33上面必须记住33的位置 解决方法,要求递归函数返回一个 结点插到33的右子树 四.二叉搜索树的删除 要是删除的是叶子节点之间删除 只有一…...

Ubuntu配置阿里云docker apt源
一、配置阿里云docker apt源 Ubuntu 放弃了apt-key的GPG 密钥的管理方法,用户可以直接添加gpg密钥到/etc/apt/trusted.gpg.d/目录下。 同时添加删除apt source 直接在/etc/apt/sources.list.d/目录下操作即可。 1、删除旧的镜像源 #旧版操作方法 apt-key list # …...

【React】状态管理之Redux
🌈个人主页: 鑫宝Code 🔥热门专栏: 闲话杂谈| 炫酷HTML | JavaScript基础 💫个人格言: "如无必要,勿增实体" 文章目录 状态管理之Redux引言1. Redux 的核心概念1.1 单一数据源(Single Sou…...

3195. 有趣的数-13年12月CCF计算机软件能力认证(组合数)
题目 思路 统计方案的时候先去分类,先放01,然后在考虑23对于第k类, 对于01的选择 对于所有的分类:本题我觉得要考虑的几个点就是:状态分类得到数学公式组合数的计算防越界处理 代码 计算组合数的代码模板࿱…...

基于 Python 的 Bilibili 评论分析与可视化
一、项目概述 本项目利用 Python 对 Bilibili (哔哩哔哩)平台上的视频评论数据进行爬取、清洗和分析,并通过可视化展示数据的主要特征。我们通过以下几个步骤实现了这一过程: 数据爬取:使用 Bilibili 提供的 API 获取…...

大语言模型理论基础
文章目录 前言大语言模型必需知识概述大语言模型目标模型上下文神经网络的神经元常见激活函数SigmoidTanhRelusoftmax 通用近似定理多层感知机(MLP)拟合最后 前言 你好,我是醉墨居士,我们接下来对大语言模型一探究竟,…...

【 LLM论文日更|检索增强:大型语言模型是强大的零样本检索器 】
论文:https://aclanthology.org/2024.findings-acl.943.pdf代码:GitHub - taoshen58/LameR机构:悉尼科技大学 & 微软 & 阿姆斯特丹大学 & 马里兰大学领域:retrieval & llm发表:ACL2024 研究背景 研究…...

【基于轻量型架构的WEB开发】课程 作业3 Spring框架
一. 单选题(共12题,48分) 1. (单选题)以下有关Spring框架优点的说法不正确的是( )。 A. Spring就大大降低了组件之间的耦合性。 B. Spring是一种侵入式框架 C. 在Spring中,可以直接通过Spring配置文件管理…...

14.最长公共前缀-力扣(LeetCode)
题目: 解题思路: 解决本题的关键点是确定扫描的方式,大体上有两种方式:横向扫描和纵向扫描。 1、横向扫描:首先比较第一个字符串和第二个字符串,记录二者的公共前缀,然后用当前公共前缀与下一个…...

客户案例|智能进化:通过大模型重塑企业智能客服体验
01 概 述 随着人工智能技术的快速发展,客户对服务体验的期待和需求不断升级。在此背景下,大模型技术的崛起,为智能客服领域带来了创造性的变革。 在上篇文章《在后LLM时代,关于新一代智能体的思考》中有提到,智能客服…...

Flink Job更新和恢复
Checkpoints 的主要目的是为意外失败的作业提供恢复机制。 Savepoints的设计更侧重于可移植性和操作灵活性,尤其是在 job 变更方面。Savepoint 的用例是针对计划中的、手动的运维。例如,可能是更新你的 Flink 版本,更改你的作业图等等。 fli…...

读多写少业务中,MySQL如何优化数据查询方案?
小熊学Java站点:https://www.javaxiaobear.cn 编程资料合集:https://pqgmzk7qbdv.feishu.cn/base/QXq2bY5OQaZiDksJfZMc30w5nNb?from=from_copylink 看一看当面试官提及“在读多写少的网络环境下,MySQL 如何优化数据查询方案”时,你要从哪些角度出发回答问题??? 案例…...

Bugku CTF_Web——点login咋没反应
Bugku CTF_Web——点login咋没反应 进入靶场 随便输个试试 看来确实点login没反应 抓包看看 也没有什么信息 看了下源码 给了点提示 一个admin.css try ?12713传参试试 拿到一个php代码 <?php error_reporting(0); $KEYctf.bugku.com; include_once("flag.php&q…...

attention 注意力机制 学习笔记-GPT2
注意力机制 这可能是比较核心的地方了。 gpt2 是一个decoder-only模型,也就是仅仅使用decoder层而没有encoder层。 decoder层中使用了masked-attention 来进行注意力计算。在看代码之前,先了解attention-forward的相关背景知识。 在普通的self-atten…...

什么是HTTP,什么是HTTPS?HTTP和HTTPS都有哪些区别?
什么是 HTTP? HTTP(Hypertext Transfer Protocol,超文本传输协议)是一种应用层协议,用于在互联网上进行数据通信。它定义了客户端(通常是浏览器)和服务器之间的请求和响应格式。HTTP 是无状态的…...

SkyWalking-安装
SkyWalking-简单介绍 是一个开源的分布式追踪系统,用于检测、诊断和优化分布式系统的功能。 支持 ElasticSearch、H2、MySQL、PostgreSql 等数据库 基于 ElasticSearch 的情况 ElasticSearch(ES) 安装 1、下载并解压 https://www.elastic…...

RabbitMQ运维
1. 单机多节点 1.1 搭建RabbitMQ ①安装RabbitMQ 略 ②确认RabbitMQ运⾏没问题 #查看RabbitMQ状态 rabbitmqctl status 节点名称: 端口号: 25672:Erlang分布式节点通信的默认端⼝, Erlang是RabbitMQ的底层通信协议.15672: Web管理界⾯的默认端⼝, 通过这个端⼝可以访问R…...

Go语言并发精髓:深入理解和运用go语句
Go语言并发精髓:深入理解和运用go语句 在Go语言的世界里,go语句是实现并发的核心,它简洁而强大,允许程序以前所未有的方式运行多个任务。本文将深入探讨go语句及其执行规则,揭示Go语言并发编程的内在机制,并提供实际案例帮助读者掌握其用法。 1. go语句的基本概念(Wha…...

基于STM32的智能家居系统:MQTT、AT指令、TCP\HTTP、IIC技术
一、项目概述 随着智能家居技术的不断发展,越来越多的家庭开始使用智能设备来提升生活质量和居住安全性。智能家居系统不仅提供了便利的生活方式,还能有效地监测家庭环境,保障家庭安全。本项目以设计一种基于STM32单片机的智能家居系统为目标…...

分糖果(相等分配)
题目:有n种不同口味的糖果,第i种糖果的数量为a[i],现在需要把糖果分给m个人。分给每个人糖果的数量必须是相等的,并且每个人只能选择一种糖果。也就是说,可以把一种糖果分给多个人,但是一个人的糖果不能有多…...

docker构建jdk11
# 建立一个新的镜像文件,配置模板:新建立的镜像是以centos为基础模板 # 因为jdk必须运行在操作系统之上 FROM centos:7.9.2009# 作者名 MAINTAINER yuanhang# 创建一个新目录来存储jdk文件 RUN mkdir /usr/local/java#将jdk压缩文件复制到镜像中&#…...

唐帕科技校园语音报警系统:通过关键词识别,阻止校园霸凌事件
校园霸凌问题已成为全球教育领域的严峻挑战,给受害者带来了身心上的长期创伤。然而,随着科技的发展,尤其是人工智能和语音识别技术的不断进步,我们开始看到创新性解决方案的出现。校园语音报警系统便是其中一种利用技术手段保护学…...

酒店行业数据仓库
重要名词: PMS:酒店管理系统CRS:中央预定系统客户:可以分为会员、散客(自行到店入住)、协议(与酒店长期合作,内部价)、中介预定:可以分为线上预定、线下预定…...

A029-基于Spring Boot的物流管理系统的设计与实现
🙊作者简介:在校研究生,拥有计算机专业的研究生开发团队,分享技术代码帮助学生学习,独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取,记得注明来意哦~🌹 赠送计算机毕业设计600…...

Python Day5 进阶语法(列表表达式/三元/断言/with-as/异常捕获/字符串方法/lambda函数
Python 列表推导式是什么 列表推导式是 Python 语言特有的一种语法结构,也可以看成是 Python 中一种独特的数据处理方式, 它在 Python 中用于 转换 和 过滤 数据。 其语法格式如下所示,其中 [if 条件表达式] 可省略。 [表达式 for 迭代变量…...

一文了解Android的核心系统服务
在 Android 系统中,核心系统服务(Core System Services)是应用和系统功能正常运行的基石。它们负责提供系统级的资源和操作支持,包含了从启动设备、管理进程到提供应用基础组件的方方面面。以下是 Android 中一些重要的核心系统服…...

Scala的Array(1)
Scala的Array表示长度不可变的数组,若需要定义可变数组需要倒包 import scala.collection.mutable.ArrayBuffer 下面是关于Array的一些用法: import scala.collection.mutable.ArrayBufferobject test29 { // //不可变数组 Array // def main(args:…...