当前位置: 首页 > 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 函数中的循环会一直运行。

相关文章:

  • 接口01-Java
  • Matlab R2022b 安装成功小记
  • 力扣101. 对称二叉树
  • Java中的spring——面试题+答案(Spring Boot)——第20期
  • LeetCode(32)串联所有单词的子串【滑动窗口】【困难】(含图解)
  • 【MATLAB源码-第89期】基于matlab的灰狼优化算法(GWO)无人机三维路径规划,输出做短路径图和适应度曲线
  • 域名和ip的关系
  • Ajax 是什么? 如何创建一个 Ajax?
  • Docker 命令详解
  • 小程序如何禁止指定用户访问?如何设置指定用户才能访问?
  • 【虚拟机】在VM中安装 CentOS 7
  • 如何使用 Java 在Excel中创建下拉列表
  • Linux CenTOS命令备忘
  • Go语言的学习笔记2——Go语言源文件的结构布局
  • 【100个Cocos实例】编码不规范,接手泪两行...
  • “Material Design”设计规范在 ComponentOne For WinForm 的全新尝试!
  • Brief introduction of how to 'Call, Apply and Bind'
  • centos安装java运行环境jdk+tomcat
  • CSS3 聊天气泡框以及 inherit、currentColor 关键字
  • Docker 笔记(2):Dockerfile
  • es6--symbol
  • flutter的key在widget list的作用以及必要性
  • HTML-表单
  • iOS小技巧之UIImagePickerController实现头像选择
  • JavaScript标准库系列——Math对象和Date对象(二)
  • java中的hashCode
  • learning koa2.x
  • Mysql数据库的条件查询语句
  • npx命令介绍
  • PAT A1092
  • Python_OOP
  • Webpack 4x 之路 ( 四 )
  • weex踩坑之旅第一弹 ~ 搭建具有入口文件的weex脚手架
  • 基于遗传算法的优化问题求解
  • 普通函数和构造函数的区别
  • 前嗅ForeSpider中数据浏览界面介绍
  • 说说动画卡顿的解决方案
  • 它承受着该等级不该有的简单, leetcode 564 寻找最近的回文数
  • 无服务器化是企业 IT 架构的未来吗?
  • 新手搭建网站的主要流程
  • 责任链模式的两种实现
  • 智能合约Solidity教程-事件和日志(一)
  • 翻译 | The Principles of OOD 面向对象设计原则
  • 昨天1024程序员节,我故意写了个死循环~
  • # Swust 12th acm 邀请赛# [ A ] A+B problem [题解]
  • (+4)2.2UML建模图
  • (1)Map集合 (2)异常机制 (3)File类 (4)I/O流
  • (1)安装hadoop之虚拟机准备(配置IP与主机名)
  • (MATLAB)第五章-矩阵运算
  • (附源码)ssm基于jsp的在线点餐系统 毕业设计 111016
  • (附源码)流浪动物保护平台的设计与实现 毕业设计 161154
  • (强烈推荐)移动端音视频从零到上手(上)
  • (三十五)大数据实战——Superset可视化平台搭建
  • (原)记一次CentOS7 磁盘空间大小异常的解决过程
  • (轉貼) 寄發紅帖基本原則(教育部禮儀司頒布) (雜項)