陈银波的知识小站

  • 首页
  • 数学
  • 算法
  • 图
  • 数据
复杂 = f (简单1, 简单2, ... , 简单n)
  1. 首页
  2. 工程
  3. 正文

Go:goroutine 与 channel 的优雅并发编程实践

8 9 月, 2024 929点热度 0人点赞 0条评论

0 前言

过去一段时间,我经常涉及使用 Go 语言进行并发编程的一些工作。在完成这些项目的过程中,逐渐了解和积累了 Go 语言中使用 goroutine 和 channel 进行并发编程的一些经验,这里分享给大家,供大家学习和交流。

1 并发编程实践

1.1 按任务划分 goroutine 职责

假设现在有 n 个任务,每个任务的内容都是读取某个文件,然后把文件内容写入数据库。

在其他一些主流的编程语言,可能会启动多个线程,每个线程完成其中的一部分任务,通过这种方式利用多核并行加速完成任务。

但是在 go 语言中,更鼓励的做法是为不同的任务启动不同的 goroutine,在这个任务中,可以启动一个或多个 goroutine来做读取文件操作,一个或多个 goroutine来做写入数据库操作,中间通过 go 程进行通信。

关于如何划分 gorutinue 职责是一个并发模型设计问题,有兴趣的朋友可以读下 Rob Pike(Go作者之一) 于2012年发表的一场演讲,名为《并发不是并行》。

1.2 管道模式

管道模式是一种数据流式处理的方式,其中一系列的 goroutine 每个都处理输入数据的一部分,并将其传递给下一个 goroutine。这种模式非常适合于处理大量数据的批处理任务。

管道模式可以看作是生产者-消费者模式的一种扩展形式,特别是在数据处理流水线中。在传统的生产者-消费者模式中,通常有一个生产者将数据放入一个共享队列(如channel),然后一个或多个消费者从中取出数据并处理。

管道模式则更进一步,它不仅仅是在生产者和消费者之间建立一个简单的队列关系,而是构建了一个由多个阶段组成的流水线。每个阶段都可以视为一个小型的生产者-消费者系统,其中前一阶段的输出作为后一阶段的输入。这种模式特别适用于数据需要经过多步处理的情况,每一步可能由不同的 goroutine 来完成,这样就可以实现数据的流水线式处理。

常见的场景比如说,Extract-Transform-Load,也就是大家常说的 ETL。

1.3 子任务单 goroutine 管道模式使用 close 管理退出

有一些说法强调不要使用 close 来关闭 channel。但事实上,在管道模式,使用 close 来作为通知机制是方便和有效的。

当管道模式中的每个有作为生产者子任务都只有一个 goroutine 在做时,比如说上面的读取文件写入数据库的例子,如果只有一个 goroutine 在读取文件。那么,该 goroutine 在读取完所有文件后,便可以通过调用 close 来关闭 channel,这样便可以通知到写入数据库的 goroutine channel 已经关闭,只要取完所有数据并处理完,就可以退出了。

当然,使用 close 确实有需要注意的:

竞态条件:如果有多个goroutine尝试关闭同一个channel,可能会引发竞态条件。

不可恢复性:一旦channel被关闭,就不能再向其中发送数据。

不能因噎废食,学会理解它们的使用机制并正确合理地使用它们。

1.4 子任务多 goroutine 管道模式使用 close + sync.WaitGroup 来管理退出

现在有一个 Extract-Transform-Load 场景,有 Extract、Transform、Load 三个子任务。

假设现在我们使用了 i 个 goroutine 在进行 Extract 任务,使用了 j 个 goroutine 在进行 Transform 任务,使用了 k 个 goroutine 在进行 Load 任务。

对上面每个有作为生产者的子任务(Extract、Transform)分配一个 sync.WaitGroup。在主 goroutine 中,先把所有子 goroutine 启动起来,然后使用 Wait 方法等待 Extract 的任务完成后,关闭 Extract 到 Transform 的 channel。接着再使用 Wait 方法等待 Transform 任务完成后,关闭 Transform 到 Load 的 channel。

1.5 优雅地中断

一般来讲,既然开了一个 goroutine 来完成任务,计算时间一般会稍长一些,这种时候,常常会要求能快速中断 goroutine。

一种最常见的在 goroutine 中进行中断的方式是,在 goroutine 中寻找最耗时的循环执行代码,然后在这代码中使用 select context.Context 捕获中断信号。

具体地,我们可以在主 goroutine 中创建一个带有取消功能的 context.Context,将该 context.Context 传入子 goroutine。在需要停止时,主 goroutine 调用 cancel 函数。而子 goroutine 通过检查 ctx.Done() 来决定是否继续运行。

1.6 使用 chan error 来传递错误

goroutine 无法直接返回结果,因此常常通过 channel 返回结果。同样地,goroutine 无法直接返回 error,因此常常通过 chan error 来返回结果。

1.7 不要让 goroutine 数量泛滥

虽然 goroutine 的代价很小,但过多的 goroutine 会带来更多的开销。在实际编程中,应该对 goroutine 的数量进行控制,不要让 goroutine 的数量泛滥。

使用固定的或者有上限的 goroutine 数量,而不是让 goroutine 数量随着任务数量增大而不断增大。

在可能的情况下,重用而不是创建新的 goroutine。例如,可以使用工作池(Worker Pool)的方式来管理一定数量的 goroutine。

2 结束

这是目前的一些我在并发编程实践中的一些经验总结,希望对大家有帮助。这篇文章我写地相对简洁,哪里需要代码示例的可以在评论提出,我可以进行补充。

标签: 暂无
最后更新:9 9 月, 2024

陈银波

邮箱:agwave@foxmail.com 知乎:https://www.zhihu.com/people/agwave github:https://github.com/agwave leetcode:https://leetcode.cn/u/agwave

点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

文章目录
  • 0 前言
  • 1 并发编程实践
    • 1.1 按任务划分 goroutine 职责
    • 1.2 管道模式
    • 1.3 子任务单 goroutine 管道模式使用 close 管理退出
    • 1.4 子任务多 goroutine 管道模式使用 close + sync.WaitGroup 来管理退出
    • 1.5 优雅地中断
    • 1.6 使用 chan error 来传递错误
    • 1.7 不要让 goroutine 数量泛滥
  • 2 结束
分类
  • 图
  • 工程
  • 数学
  • 数据
  • 算法
最新 热点 随机
最新 热点 随机
Change Data Capture (CDC) 技术初探 IPv6在物联网中的应用 IPv6首部的改进:简化与优化网络通信 IPv6:下一代互联网协议 联邦图学习:连接联邦学习与图神经网络的新桥梁
二次型化标准型的应用:最值求解图注意力网络(GAT):一个例子解释从输入到输出维度变化的完整过程图卷积网络(GCN):一个例子解释从输入到输出维度变化的完整过程联邦图学习:连接联邦学习与图神经网络的新桥梁IPv6首部的改进:简化与优化网络通信
一笔画问题揭秘:轻松掌握欧拉图与欧拉回路的奥秘 高阶导数题四大解法一文搞定 二次型化标准型的应用:最值求解 遗传算法解决旅行商问题 Go:goroutine 与 channel 的优雅并发编程实践
归档
  • 2024 年 10 月
  • 2024 年 9 月
  • 2024 年 8 月
  • 2024 年 7 月
  • 2024 年 6 月
  • 2024 年 5 月

COPYRIGHT © 2024 陈银波的知识小站. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

粤ICP备2024254302号-1

粤公网安备44030002003798号