GO 语言协程知识点学习笔记

devtools/2024/10/19 20:09:49/

GO 语言协程知识点学习笔记 是个人从互联网上学习整理的笔记。因个人技艺不精,如有纰漏,还请斧正。

协程?

协程并不是 GO 语言特有的机制,像 Lua、Ruby、Python、Kotlin、C/C++ 也都有协程的支持。区别在于有些是从语言层面支持,有的通过插件类库支持。Go 语言的协程是原生语言层面支持的。

协程和进程和线程的对比
进程
  • 进程是系统资源分配的最小单位。进程的创建和销毁都是系统资源级别。操作起来代价昂贵。程是抢占式调度,分为三个状态:等待态、就绪态、运行态。进程之间是相互隔离的,拥有各自的系统资源,更加安全。因此存在进程之间通讯不方便的问题
  • 进程是线程的载体容器,多个线程除了共享进程的资源还拥有自己的一少部分独立的资源。因此相比进程而言线程更加清凉,进程内的多个线程间的通信比进程容易,但也同样带来了同步和互斥的问题和线程安全的问题(多线程编程的常见问题),尽管多线程编程仍然是当前服务端编程的主流,线程也是 CPU 调度的最小单位,多线程运行时存在线程切换的问题,其状态的转移如图:

协程
  • 协程在有的资料中成为微线程或者用户态轻量级线程,协程调度不需要系统内核参与而是完全由用户态程序来决定,因此协程对于系统而言是无感知的。协程由用户态控制就不存在抢占式调度。抢占式调度会强制让 CPU 控制权切换到其它进线程,多个协程进行协作实调度,协程自己主动把控制权转让出去后,其它协程 才能被执行到,这样就避免了系统切换开销提高了 CPU 的使用效率。
抢占式调度和协作式调度的简单对比图:

Go 协程

启用一个 Go 协程非常简单,只需要在函数前加上关键字 go,就可以轻易的启用一个新的 Go 协程并发运行。

package mainimport "fmt"func main() {go hello()fmt.Println("main function")
}func hello() {fmt.Println("Hello goroutine")
}

go hello() 此处启动了一个协程,因此hello() 函数会与 main() 函数并发执行。main() 函数会运行在一个特有的 Go 协程上,它被称为 Go 的主协程(Main Goroutine)。

运行结果大概率如下:

main functionProcess finished with exit code 0

为什么只输出了 main function ?我们的 Hello goroutine 为什么没有输出呢?

  • 当启动一个新的协程时,协程的调用会立即返回,与函数不同,程序控制不回去等待 Go 的协程执行完毕,在本例中即 hello() 函数,因此程序控制会立即返回到代码的下一行,即 fmt.Println("main function") ,忽略该协程的任何返回值
  • 协程的存在与否取决于 main() 主协程是否存活,如果主协程被销毁(即程序终止了),那么其余用 go 关键字启用的协程,在本例中为 hello() 协程也被销毁了。这就导致了主协程已经结束,但没有完全等待 hello() 协程的执行完毕,就被销毁了。因此出现了以上情况。

看到这里,你应该知道为什么运行结果是大概率如下,因为主协程和协程的执行顺序是不一致的,在一定概率下,即主协程正好等到了协程的执行完毕,会输出 Hello goroutine

让我们来修复下这个问题吧!

package mainimport ("fmt""time"
)func main() {go hello()time.Sleep(1 * time.Second)fmt.Println("main function")
}func hello() {fmt.Println("Hello goroutine")
}

可以看到,我们利用 time 包中的 Sleep 方法,去休眠主协程,让其等待 1 秒钟,等待 hello() 协程执行完毕。那么最终输出结果如下:

Hello goroutine
main functionProcess finished with exit code 0

注意:在实际业务中,利用协程休眠的方式去控制协程的执行顺序是不可取的,因为这样的预测永远无法 100% 如你所愿。如果需要控制协程的同步,我们会在下面讲到 channel 即管道或信道。

启用多个 Go 协程

为了更好的理解 Go 协程,我们再编写一个程序,启动多个 Go 协程

package mainimport ("fmt""time"
)func main() {go hello()go anotherHello()time.Sleep(1 * time.Second)fmt.Println("main function")
}func hello() {for i := 0; i <= 3; i++ {time.Sleep(250 * time.Millisecond)fmt.Println("Hello goroutine")}
}func anotherHello() {for i := 0; i <= 3; i++ {time.Sleep(400 * time.Millisecond)fmt.Println("Hello another goroutine")}
}
  • hello() 协程会在第一次输出 Hello goroutine 后,休眠该协程 250 毫秒,再次输出 Hello goroutine ,直到 i > 3
  • anotherHello() 协程会在第一次输出 Hello another goroutine 后,休眠该协程 400 毫秒,再次输出 Hello another goroutine ,直到 i > 3
  • 最终主协程会在该两个协程执行完毕后,输出 main function

是不是很简单?Go 语言屏蔽了多线程的复杂实现,只需要一个 go 关键字即可轻而易举的创建一个新的协程。

什么是 Channel 管道:

Channel 是 Go 协程之间通信的通信管道,如同管道中的谁会从一端流到另一端,通过使用管道,就可以实现数据从管道的一个端口发送,在另外一个端口接收。

管道声明(无缓冲管道)

声明管道就像声明一个切片一样简单:

package mainfunc main() {c := make(chan int)// 或者var c2 chan int
}
  • chan T 表示该管道只能运送 T 类型的数据,在本例中为 int 类型数据
通过管道进行发送与接收
a := make(chan int)
getDataFromChannelA := <-a // 读取管道 a 发送的内容
a <- 1                     // 向管道 a 发送 1 

怎么样?是不是很简单。在一开始可能会觉得管道操作符比较难记,其实我们只需要记住简单的方法即可

  • a <- 1 向管道 a 发送 1,箭头指向管道,即代表发送
  • <- a 从管道 a 读取数据,箭头不指向管道,即代表从管道接收
发送与接收默认是阻塞的

发送于接收默认是阻塞的,这是什么意思呢?当把数据发送至管道时,程序控制会产生阻塞,直到有另外一个协程(可以是主协程)来进行接收,直到其它协程接收了来自管道的数据,反之亦然。否则程序才能继续运行,否则将一直阻塞。

利用该特性,我们可以很容易实现一个不用加锁的同步的协程通信的管道。那么我们如果实现一个发送与接受不阻塞的管道呢?其实只需要将管道增加缓冲即可。


http://www.ppmy.cn/devtools/125035.html

相关文章

Spring Cloud Stream 3.x+kafka 3.8整合

Spring Cloud Stream 3.xkafka 3.8整合&#xff0c;文末有完整项目链接 前言一、如何看官方文档(有深入了解需求的人)二、kafka的安装tar包安装docker安装 三、代码中集成创建一个测试topic&#xff1a;testproducer代码producer配置&#xff08;配置的格式&#xff0c;上篇文章…

java.io.StreamCorruptedException: invalid stream header的原因及解决方法

最近在写一个类似于QQ的网络通讯项目&#xff0c;在信息发送的时候出现了一个问题&#xff0c;客户端的信息服务端可以正常收到并且转出&#xff0c;但是对应的客户端在接收的时候就会抛出这个异常&#xff0c;往往还会伴随着java.io.StreamCorruptedException: invalid type c…

如何彻底删除360软件或安装的应用软件

以彻底删除360安全软件为例&#xff08;或其他360相关软件&#xff09;&#xff0c;可以按照以下步骤进行操作&#xff1a; 方法一&#xff1a;通过控制面板卸载 打开控制面板&#xff1a; 在 Windows 操作系统中&#xff0c;右键点击开始菜单&#xff0c;选择“控制面板”。或…

vscode播放MP4文件时候没声音

问题描述&#xff1a; vscode 播放MP4文件时候没有声音 原因分析&#xff1a; https://github.com/microsoft/vscode-docs/blob/vnext/release-notes/v1_72.md#built-in-preview-for-some-audio-and-video-files 解决方案&#xff1a; 从上面描述可以看出&#xff0c;大概…

vue3 vue2

vue3.0是如何变快的&#xff1f; diff算法优化 vue2的虚拟dom是进行全局的对比。vue3 新增了静态标记&#xff08;patchFlag&#xff09; 在与上次虚拟节点进行比较的时候&#xff0c;只对比带有patch Flag的节点&#xff0c;并且可以通过flag的信息得知当前节点要对比的具体内…

Pandas库详细学习要点

Pandas库是一个基于Python的数据分析库&#xff0c;它提供了丰富的数据结构和数据分析工具&#xff0c;非常适合数据科学和数据分析领域的工作。以下是Pandas库详细学习的一些要点&#xff1a; 1. 数据结构 - Series&#xff1a;一维带标签数组&#xff0c;类似于NumPy中的一…

verdaccio使用管理私自npm

参考文献&#xff1a; 使用verdaccio搭建自己的npm私有库_one of the uplinks is down, refuse to publish-CSDN博客 npm i报错request to https://registry.npmjs.org/xxx failed, reason: connect ETIMEDOUT 104.16.25.34:443-CSDN博客 window上搭建npm私仓&#xff08;verd…

vue双向绑定/小程序双向绑定区别

Vue双向绑定与小程序双向绑定在实现方式、语法差异以及功能特性上均存在显著区别。以下是对这两者的详细比较&#xff1a; 一、实现方式 Vue双向绑定 Vue的双向绑定主要通过其响应式数据系统实现。Vue使用Object.defineProperty()方法&#xff08;或在Vue 3中使用Proxy对象&am…