当前位置: 首页 > news >正文

Golang并发模型:Goroutine 与 Channel 初探

文章目录

    • goroutine
      • goexit()
    • channel
      • 缓冲
      • close
      • range
      • select

goroutine

goroutine 是 Go 语言中的一种轻量级线程(lightweight thread),由 Go 运行时环境管理。与传统的线程相比,goroutine 的创建和销毁的开销很小,可以轻松创建成千上万个 goroutine,而不会导致系统性能下降。

以下是一些关于 goroutine 的重要特性:

  1. 轻量级: 每个 goroutine 的栈大小初始时只有几 KB,可以根据需要进行动态扩展和收缩。

  2. 并发执行: Go 语言通过 goroutine 实现并发编程,允许在程序中同时执行多个任务。通过 goroutine,可以更方便地编写并发程序。

  3. 独立调度: Go 运行时具有自己的调度器,它负责在多个 goroutine 之间进行协作式调度。与操作系统线程不同,goroutine 的调度是在用户空间进行的,减少了上下文切换的成本。

  4. 通信通过通道: 多个 goroutine 之间通过通道进行通信。通道是一种数据结构,用于在 goroutine 之间传递数据。通过使用通道,可以更安全、更简单地实现 goroutine 之间的通信和同步。

以下是一个简单的示例,演示了如何创建和运行一个 goroutine:

package mainimport ("fmt""time"
)// 子goroutine
func newTask() {for i := 0; ; i++ {fmt.Printf("new goroutine:i = %d\n", i)time.Sleep(1 * time.Second)}
}// 主goroutine
func main() {// 创建一个go程 去执行newTask()流程go newTask()for i := 0; ; i++ {fmt.Printf("main goroutine:i = %d\n", i)time.Sleep(1 * time.Second)}
}

由运行结果可以看出,newTask 函数被启动为一个 goroutine,与主 goroutine 同时执行。由于 goroutine 是并发执行的,输出将交替显示两个 goroutine 的执行结果。
请添加图片描述
但是,当主goroutine执行任务完成退出比子goroutine早时,程序就会直接终止,而不再等待其他的 goroutine 执行完毕。如以下代码所示。

package mainimport ("fmt""time"
)// 子goroutine
func newTask() {for i := 0; ; i++ {fmt.Printf("new goroutine:i = %d\n", i)time.Sleep(1 * time.Second)}
}// 主goroutine
func main() {// 创建一个go程 去执行newTask()流程go newTask()fmt.Println("main goroutine exit")
}
/*输出结果
main goroutine exit
*/

goexit()

runtime.Goexit() 是 Go 语言 runtime 包提供的一个函数,用于立即终止当前 goroutine 的执行。调用 Goexit 会导致当前 goroutine 进行善后工作(执行已注册的 defer 语句)并返回,而不会影响其他正在运行的 goroutine。

package mainimport ("fmt""runtime""time"
)func main() {// 用go创建一个形参为空,返回值为空的函数go func() {defer fmt.Println("A.defer")func() {defer fmt.Println("B.defer")runtime.Goexit()fmt.Println("B")}()fmt.Println("A")}()for {time.Sleep(1 * time.Second)}
}
/*输出结果
B.defer
A.defer
*/

在 goroutine 内部的第二个 defer 语句 defer fmt.Println("B.defer") 表示在该 goroutine 返回之前执行。然后,在这个 goroutine 内部的匿名函数中调用了 runtime.goexit(),这会立即结束当前 goroutine 的执行。

由于 runtime.goexit()的调用,后面的代码不再执行,包括 “B” 的打印语句和 “A” 的打印语句。

channel

在Go语言中,channel 是一种用于在 goroutine 之间进行通信和同步的数据结构。它提供了一种安全、简单且高效的方式,使得不同的 goroutine 之间能够传递数据和同步操作。

channel的定义:c := make(chan int)

下面是进行 goroutine 间通信的基本例子。因为 channel 是阻塞的,所以在主 goroutine 中接收数据的操作 num := <-c 会等待直到 goroutine 发送完数据为止。

package mainimport "fmt"func main() {c := make(chan int)go func() {defer fmt.Println("goroutine end")fmt.Println("goroutine正在运行")c <- 666 //将666发送给c}()num := <-c //从c中接收数据,并赋值给numfmt.Println("num =", num)fmt.Println("main goroutine end")
}
/*输出结果
goroutine正在运行
goroutine end
num = 666
main goroutine end
*/

缓冲

上面例子中的channel是一个无缓冲的,其运行的具体过程如下图所示。
请添加图片描述
在给channel加上缓冲后,运行过程如下图。
请添加图片描述

下面这段代码演示了使用带有缓冲的 channel 进行 goroutine 间通信的例子。由于通道带有缓冲,即使没有立即被接收,子 goroutine 依然可以向通道发送多个数据,直到达到缓冲容量。在主 goroutine 中,使用缓冲通道可以使得发送和接收操作更灵活,不需要等待另一方立即接收。

package mainimport ("fmt""time"
)func main() {c := make(chan int, 3) // 带有缓冲的channelfmt.Println("len(c) =", len(c), "cap(c) =", cap(c))go func() {defer fmt.Println("子go程结束")for i := 0; i < 3; i++ {c <- ifmt.Println("子go程正在运行,len(c) =", len(c), "cap(c) =", cap(c))}}()time.Sleep(2 * time.Second)for i := 0; i < 3; i++ {num := <-cfmt.Println("num =", num)}fmt.Println("主go程结束")
}
/*运行结果
len(c) = 0 cap(c) = 3
子go程正在运行,len(c) = 1 cap(c) = 3
子go程正在运行,len(c) = 2 cap(c) = 3
子go程正在运行,len(c) = 3 cap(c) = 3
子go程正在运行,len(c) = 3 cap(c) = 3
子go程结束
num = 0
num = 1
num = 2
num = 3
主go程结束
*/

close

close 函数用于关闭一个通道。

package mainimport "fmt"func main() {c := make(chan int)go func() {for i := 0; i < 3; i++ {c <- i}close(c) // close可以关闭一个channel}()for {// ok如果为true表示channel没有关闭,如果为false表示已经关闭if data, ok := <-c; ok {fmt.Println(data)} else {break}}fmt.Println("main goroutine end")
}

channel无需像文件一样经常关闭,只有确实不需要发数据了,或者想显式结束range循环,才去关闭channel。
关闭channel后,无法再向channel发送数据,但是可以继续接收数据。
对于nil channel,无论收发都会被阻塞。

range

当使用 range 迭代通道时,它会一直阻塞,直到通道关闭且所有的元素都被读取完毕。需要注意的是,在使用 range 迭代通道时,通道必须在某个地方被关闭,否则 range 不会结束。否则,它会一直等待新的数据,导致程序阻塞

package mainimport "fmt"func main() {c := make(chan int)go func() {for i := 0; i < 3; i++ {c <- i}close(c) // close可以关闭一个channel}()// 可以使用range来迭代不断操作channelfor data := range c {fmt.Println(data)}fmt.Println("main goroutine end")
}

select

单流程下一个go只能监控一个channel的状态,select可以完成监控多个channel

基本语法:

select {
case <- chan1:// 如果chan1成功读取到数据,就会进入该case并处理
case chan2 <- 1:// 如果成功向chan2写入数据,就会进入该case并处理
default:// 如果上面都没有成功,就进入default处理
}

下面是一个通过 Go 语言的 select 语句和通道(channel)来生成斐波那契数列的例子。

package mainimport "fmt"func fibonacii(c, quit chan int) {x, y := 1, 1for {select {case c <- x:// 如果c可写,那么就会进入该casex = yy = x + ycase <-quit:fmt.Println("quit")return}}
}func main() {c := make(chan int)quit := make(chan int)go func() {for i := 0; i < 10; i++ {fmt.Println(<-c)}quit <- 0}()fibonacii(c, quit)
}

这个程序的输出是斐波那契数列的前 10 个数字,然后输出 “quit”。由于 main 函数中的 goroutine 先执行,因此在 quit 通道被关闭之前,fibonacci 函数中的循环会一直运行。

http://www.lryc.cn/news/242196.html

相关文章:

  • 批量添加PPT备注
  • 数据挖掘之PCA-主成分分析
  • 人工智能-注意力机制之注意力汇聚:Nadaraya-Watson 核回归
  • <HarmonyOS第一课>1·运行Hello World【课后考核】
  • Ubuntu18.04安装A-Loam保姆级教程
  • 重生之我是一名程序员 40 ——字符串函数(1)
  • Navicat 技术指引 | 连接 GaussDB 主备版
  • 【git】pip install git+https://github.com/xxx/xxx替换成本地下载编译安装解决网络超时问题
  • SQL Server对象类型(6)——4.6.存储过程和函数(Procedure和Function)
  • spring @Async异步执行
  • #Js篇:单线程模式同步任务异步任务任务队列事件循环setTimeout() setInterval()
  • html table样式的设计 表格边框修饰
  • 2023年【危险化学品经营单位安全管理人员】考试内容及危险化学品经营单位安全管理人员最新解析
  • 腾讯云 小程序 SDK对象存储 COS使用记录,原生小程序写法。
  • 【uniapp】本地资源图片无法通过 WXSS 获取,可以使用网络图片,或者 base64,或者使用image标签
  • 深入了解Spring Cloud中的分布式事务解决方案
  • 安装compiler version 5
  • 关闭vscode打开的本地服务器端口
  • VUE3+Springboot实现SM2完整步骤
  • CSS-背景属性篇
  • KyLin离线安装OceanBase
  • 插件预热 | 且看安全小白如何轻松利用Goby插件快速上分
  • pytorch下载离线包的网址
  • 【docker下安装jenkins】(一)
  • 【前端】必学知识ES6 1小时学会
  • 【学生成绩管理】数据库示例数据(MySQL代码)
  • 【电子通识】什么是物料清单BOM(Bill of Material))
  • 接口自动化测试难点:数据库验证解决方案!
  • 淘宝、1688代购系统;微信代购小程序,代购系统源代码,PHP前端源码演示
  • LED驱动控制专用电路