Live2D

Go语言并发编程-cnblog

并发编程

并发 vs 并行

image-20230516153920360

举个形象点的例子

  • 并发可以理解为一边吃饭,一边喝水,因为人只有一个嘴一个咽喉,所以同一时刻饭和水只能有一样进入,二者只能交替进行
  • 并行可以理解为一边走路一边吃东西,因为走路是靠腿脚,吃东西是靠嘴,二者不相干,相当于两个独立的线程,因而可以同时进行

Go语言实现了并发性能提高的调度模型,通过高效的调度,可以充分发挥多核优势,高效运行,可以说Go语言就是为并发而生的

Goroutine

Go语言中实现高并发有一个重要概念叫协程

image-20230516153944087

  • 线程属于内核态,它的创建、切换、停止都属于很重的系统操作,比较消耗资源,消耗在MB级别
  • 协程属于用户态,可以理解为轻量级的线程,协程的创建和切换由Go语言本身去完成,比线程消耗资源要少很多,消耗在KB级别

快速打印goroutine 0~4

image-20230516154134546

这里我们可以通过在调用的函数前加上 go 关键字来开启一个协程来运行,
在主函数最后加上了一个 time.Sleep 函数用来保证子协程运行结束前 主线程不退出

最终输出

image-20230516154150653

可以看到是乱序的,也就是说goroutine 0~4是通过并行进行输出的

CSP(Communicating Sequential Processes)

说完协程,再来说说协程之间的通信

image-20230516154227034
Go语言是提倡通过通信共享内存而不是通过共享内存而实现通信

  • 像左图通过channel将协程进行连接,就像是传输队列,遵循先入先出,能保证收发的顺序
  • 而像右图通过共享内存实现通信,需要通过互斥量对内存进行加锁,也就是需要获取临界区的权限,这样在一定程度上会影响程序的性能。(基本上只要需要去获取锁,都会多少影响到性能)

所以通过以上两种方式,GO语言为了保证性能,选择了通过通信实现共享内存

Channel

Channel是一种引用类型,它的创建需要使用 make 关键字

image-20230516154257410
Channel又分为无缓冲通道和有缓冲通道

  • 无缓冲通道就像是快递员送快递到楼下,打电话叫我们来拿快递,过程是同步进行的,不见不散。但这样快递员必须等我们下楼拿完快递才会去送出下一份快递,等所有人来拿完才能完成工作
  • 有缓冲通道可以理解为快递员将快递放到驿站,然后通知我们来拿,这样过程就是异步进行的了,快递员在通知完所有人来拿快递后工作就结束了,至于我们什么时候来拿就影响不到他了,效率明显提升,而这个驿站就相当于是缓冲通道,当然如果缓冲通道也就是驿站满了,快递员还是要等待驿站的快递被取走才能继续向里面添加新的快递

下面我们定义两个协程

A 子协程发生0~9数字

B 子协程计算输入数字的平方

主协程输出最后的平方数

image-20230516151412031

在这里我们定义的两个通道,dest作为传输最终结果的通道采用了有缓冲通道,因为考虑到主协程作为消费者可能消费速度没有那么快,为避免消息阻塞,因而添加了缓冲

输出结果

image-20230516151440412

并发安全 Lock

现在我们进行一个测试,对变量执行2000次+1操作,5个协程并发执行

首先测试不加锁的情况

image-20230516154423705

可以看到结果并不一定正确(可能会正确,但那是偶然)

image-20230516154446287

此时我们加上锁

image-20230516151526290

可以看到,此时结果就都是正确的了

image.png

至于为什么不加锁会出现这种问题,这算是一种并发安全问题,可能会出现多个协程读取到同一个x值,然后均对其进行+1操作

例如协程1读取到x此时为50,准备将其进行+1操作,使其变为51,但在其写入51值之前,协程2也读取了x的值为50,也对50进行+1操作,这样在协程1执行完写入x=51的操作后,协程2又重复执行了写入x=51的操作。诸如此类的操作便会导致x最终的值可能会低于预期的结果

WaitGroup

前面我们为了保证在协程执行结束前主协程不退出,都采用了调用time.Sleep函数的方法

但我们并不知道子协程执行所需的一个确切时间,因此就无法精确的设定Sleep的时间

为了解决这个问题,Go语言中提供了WaitGroup

image-20230516154543169

当我们启动了n个协程任务,计数器会加上n,每执行完一个协程,计数器会减1,然后调用Wait函数来阻塞,等待其他协程执行完,当计数器为0则表示所有并发任务执行完成


这里我们再回头看之前快速打印goroutine 0~4的例子,我们就可以用计数器来优化了

image-20230516154632845


那么到这里有关Go语言的并发编程问题就结束了

posted @   这里是轩先生  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示