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

Go并发编程(上)

目录

一、go语言当中的协程

二、MPG模型介绍

三、Goroutine  的使用

 3.1  协程的开启

3.2 优雅地等待子协程结束

四、捕获子协程的panic

五、管道Channel

5.1、认识管道

5.2、Channel的遍历和关闭

5.3 、用管道实现生产者消费者模型

5.4、Channel一些使用细节和注意事项


一、go语言当中的协程

在C++中我们要实现并发编程的时候,我们通常需要自己维护一个线程池,并且需要自己去包装一个又一个的任务,同时需要自己去调度线程执行任务并维护上下文切换,这一切通常会耗费程序员大量的心智。那么能不能有一种机制,程序员只需要定义很多个任务,让系统去帮助我们把这些任务分配到CPU上实现并发执行呢?

Go语言中的goroutine就是这样一种机制,goroutine的概念类似于线程,但 goroutine是由Go的运行时(runtime)调度和管理的。Go程序会智能地将 goroutine 中的任务合理地分配给每个CPU。Go语言之所以被称为现代化的编程语言,就是因为它在语言层面已经内置了调度和上下文切换的机制


Go语言编程中你不需要去自己写进程、线程、协程,你的技能包里只有一个技能–goroutine,当你需要让某个任务并发执行的时候,你只需要把这个任务包装成一个函数,开启一个goroutine去执行这个函数就可以了,就是这么简单粗暴

二、MPG模型介绍

我们先来看一下Go语言的并发模式,发现是不同于C++的

下面我们来解释一下MPG模式当中的M、P、G分别代表什么意思

模型解释:

G0, G1,G2  谁先执行完是完全不确定的,这不像是java语言,java可以给每个线程分别设置一个优先级,然后控制线程的执行顺序,但是go的话是不行的。  程序员只能把一个协程开启,但是中间的过程是无法去决定的。

三、Goroutine  的使用

 3.1  协程的开启

两种方法开启,当然这里只是开启,并没有去等待协程的结束。

3.2 优雅地等待子协程结束

父协程结束后,子协程并不会结束。main协程结束后,所有协程都会结束。

代码演示:

var wg = sync.WaitGroup{}func Add() {defer wg.Done()time.Sleep(1 * time.Second)fmt.Println("over")
}func main() {wg.Add(2)go Add() //开启了一个协程,并没有等待结束go Add()wg.Wait()
}

四、捕获子协程的panic

何时会发生panic:

  • 运行时错误会导致panic,比如数组越界、除0。

  • 程序主动调用panic(error)。

panic会执行什么:

  1. 逆序执行当前goroutine的defer链(recover从这里介入)。

  2. 打印错误信息和调用堆栈。

  3. 调用exit(2)结束整个进程。

关于defer

  • defer在函数退出前被调用,注意不是在代码的return语句之前执行,因为return语句不是原子操作。

  • 如果发生panic,则之后注册的defer不会执行。

  • defer服从先进后出原则,即一个函数里如果注册了多个defer,则按注册的逆序执行。

  • defer后面可以跟一个匿名函数。

五、管道Channel

5.1、认识管道

管道其本质上是一个环形队列,在这里说明一下定义管道有以下节点需要注意

1.hannel本质就是一个数据结构-环形队列
2.数据是先进先出[FIFO : [first in first out]
3.线程安全,多goroutine访问时,不需要加锁,就是说channel本身就是线程安全的(编译器底部维护的)

4.channel有类型的,一个string的channel只能存放string类型数据

下面我们来看看如何定义管道

var intChan chan int //intChan用来存储int数据var mapChan chan map[int]string//mapChan用来存储map[int]string类型var perChan chan People//用来存储自定义类型Peoplevar perChan2 chan *People

在这里有以下几点需要注意

  • 管道channel 是引用类型,需要初始化以后才能插入数据,也就是make
  • 管道是有类型的,管道的类型是什么就只能写入这种类型的数据
  • 当管道写满了以后,在没有别的协程的情况下,再次写入会导致死锁
  • 在没有使用协程的情况下(取完没放入),当管道为空,再取,会报deadlock
  • 遍历管道时需要提前把管道关闭(close),否则会导致死锁

代码进行演示

func main() {ch := make(chan int, 5)//创建一个管道for i := 0; i < 5; i++ {ch <- i //在管道当中写入数据}for len(ch) > 0 {value := <-chfmt.Println(value)}ch = make(chan int) //非缓冲通道val := <-ch         //没有初始化就取数据,会发生错误的fmt.Println(val)//注意channel关闭之后不能向channel当中写入数据,否则会造成死锁/*channel支持for --range遍历但是请注意两个细节遍历时如果channel没有关闭则会出现deadlock的错误在遍历时如果channel以及关闭了则会正常的遍历数据,遍历完毕之后就会退出吧遍历*/intchan := make(chan int, 100)for i := 0; i < 100; i++ {intchan <- i//放入100个数据到channel当中}//for i := 0; i < len(intchan); i++ {//	fmt.Println(<-intchan)//	//注意这样会少50个数据所以不能这样遍历管道的长度是一直在变的//}close(intchan) //遍历其一定需要将管道关闭否则会造成死锁for v := range intchan {fmt.Println(v)}
}

5.2、Channel的遍历和关闭

1.channel的关闭:使用内置函数close可以关闭channel,当channel关闭后,就不能再向channel写数据了,但是仍然可以从该channel读取数据,就算读取数据个数大于容量,也能读取到,只是读出来的是0 ,这时候就需要用  v,ok := < - intChan,  如果管道没有关闭,是会阻塞在这一步的,也就是说ok这里不会有值

for {v, ok := <-intChan2if !ok {//证明没有数据了fmt.Println("没有数据了") fmt.Println(v) //  读出来的是0break}fmt.Println(v)}

2.channel支持for-range 遍历和普通for进行遍历,但是普通的for循环遍历, 因为取出操作本身会导致长度变化所以我们不建议使用。

3.在遍历时,如果channel没有关闭,则回出现deadlock的错误。在遍历时,如果channel已经关闭,则会正常遍历数据,遍历完后,就会退出遍历。
 

func main() {intChan := make(chan int, 3)intChan <- 1intChan <- 2close(intChan) //关闭管道//关闭管道后就不能存放了,但是可以取数据x1 := <-intChanx2 := <-intChan//x3 := <-intChan//x4 := <-intChanfmt.Println(x1)fmt.Println(x2)//fmt.Println(x3)//fmt.Println(x4)intChan2 := make(chan int, 100)for i := 0; i < 100; i++ {intChan2 <- i}//遍历管道前切记要先close//不能使用普通的for循环,因为管道的数量在动态变化的 ,但是这里如果提前直到数量是100,循环的话i<100 就行//for i := 0; i < len(intChan2); i++ {//	x := <-intChan2//	fmt.Println(x)//}close(intChan2) //遍历前切记要先关闭管道for v := range intChan2 {fmt.Println(v)}}

5.3 、用管道实现生产者消费者模型

1.开启一个Writea协程,向管道intChan中写入50个整数
2.开启一个Read协程,从管道intChan中读取writeData写入的数据。

3.注意: Write和Read操作的是同一个管道
4.主线程需要等待Write和Read协程都完成工作才能退出[管道]

代码展示:

package mainimport ("fmt"
)func Write(intChan chan int) {for i := 0; i < 50; i++ {fmt.Printf("写入数据 :%d \n", i)intChan <- i}close(intChan) //写完之后关闭管道,但是不影响取
}func Read(intChan chan int, boolChan chan bool) {for {//time.Sleep(time.Millisecond * 100) //先等一秒,为了留时间给生产者,不等可能会直接退出v, ok := <-intChanif !ok { //证明已经没有数据了fmt.Println("管道已经没有数据了。。。")break}fmt.Printf("读出了数据 :%d\n", v)}//任务完成向管道当中写入数据boolChan <- true//通知主线程该结束了close(boolChan)
}func main() {intChan := make(chan int, 10)boolChan := make(chan bool, 1) //用来标记是否执行完的管道go Read(intChan, boolChan) //开启消费者go Write(intChan)          //开启生产者for { //让主线程一直在这里等待,直到boolChan有数据了_, ok := <-boolChanif ok {break}}
}

5.4、Channel一些使用细节和注意事项

在go语言当中如果某个协程出现了异常,如果我们不做任何处理那么就会导致整个程序崩溃掉。在go语言当中我们可以使用   

     defer + recover来处理整个异常。

如果我们起了一个协程,但是这个协程出现了panic,如果我们没有捕获这个panic,就会造成整个程序崩溃,这时我们可以在goroutine中使用recover来捕获panic,进行处理,这样即使这个协程发生的问题,但是主线程仍然不受影响,可以继续执行

package mainimport ("fmt""time"
)/*go语言当中使用recover解决协程当中出现的panic导致程序崩溃的问题如果一个协程出现了异常会导致整个程序崩溃,此时我们需要使用recover来捕获这个panic这样就不会影响其它协程*/func Say() {for i := 0; i < 10; i++ {fmt.Println("hello world")}
}func Test() {//使用defer + recover捕获抛出的panicdefer func() { if err := recover(); err != nil {fmt.Println("test()协程发生错误:\n", err)}}()var myMap map[int]string //需要提前makemyMap[0] = "提升和"         //没有提前make}
func main() {go Test()go Say()time.Sleep(time.Second)
}

相关文章:

  • PLC开放式以太网通信网络状态查看工具netstat
  • git拉取项目所有分支
  • 史上第一款AOSP开发的IDE (支持Java/Kotlin/C++/Jni/Native/Shell/Python)
  • 高效的测试覆盖率:在更短的时间内最大化提高测试覆盖率
  • STM32外设系列—MPU6050角度传感器
  • Go采集代理框架
  • 2023.11.10联赛 T3题解
  • 公众号标签
  • MySQL中UUID主键的优化
  • 官媒代运营:让大众倾听品牌的声音
  • 投票助手图文音视频礼物打赏流量主小程序开源版开发
  • 春江天玺89㎡三室两厅,现代轻奢丨不止风格更是一种生活态度。福州中宅装饰,福州装修
  • Python四种常见实现排序方法,干活教程分享~
  • 【数据结构】链表经典OJ题,常见几类题型(一)
  • django+drf+vue 简单系统搭建 (2) - drf 应用
  • 分享一款快速APP功能测试工具
  • 自己简单写的 事件订阅机制
  • 《剑指offer》分解让复杂问题更简单
  • const let
  • css选择器
  •  D - 粉碎叛乱F - 其他起义
  • exif信息对照
  • k8s如何管理Pod
  • LeetCode刷题——29. Divide Two Integers(Part 1靠自己)
  • Logstash 参考指南(目录)
  • QQ浏览器x5内核的兼容性问题
  • redis学习笔记(三):列表、集合、有序集合
  • Redux 中间件分析
  • Service Worker
  • Xmanager 远程桌面 CentOS 7
  • 讲清楚之javascript作用域
  • 解决iview多表头动态更改列元素发生的错误
  • 聊一聊前端的监控
  • 前端之Sass/Scss实战笔记
  • 入门到放弃node系列之Hello Word篇
  • 深入浅出Node.js
  • 网页视频流m3u8/ts视频下载
  • 一、python与pycharm的安装
  • 运行时添加log4j2的appender
  • 06-01 点餐小程序前台界面搭建
  • C# - 为值类型重定义相等性
  • 第二十章:异步和文件I/O.(二十三)
  • ​软考-高级-信息系统项目管理师教程 第四版【第23章-组织通用管理-思维导图】​
  • #、%和$符号在OGNL表达式中经常出现
  • $$$$GB2312-80区位编码表$$$$
  • $分析了六十多年间100万字的政府工作报告,我看到了这样的变迁
  • (13)Latex:基于ΤΕΧ的自动排版系统——写论文必备
  • (论文阅读11/100)Fast R-CNN
  • (一)硬件制作--从零开始自制linux掌上电脑(F1C200S) <嵌入式项目>
  • (转)IIS6 ASP 0251超过响应缓冲区限制错误的解决方法
  • (转)Linux整合apache和tomcat构建Web服务器
  • (转)linux自定义开机启动服务和chkconfig使用方法
  • (转)Oracle存储过程编写经验和优化措施
  • (轉貼) 蒼井そら挑戰筋肉擂台 (Misc)
  • .bat批处理(八):各种形式的变量%0、%i、%%i、var、%var%、!var!的含义和区别