go高并发之路——go语言如何解决并发问题

news/2024/11/16 13:23:32/文章来源:https://www.cnblogs.com/lmz-blogs/p/18200946

一、选择GO的原因

作为一个后端开发,日常工作中接触最多的两门语言就是PHP和GO了。无可否认,PHP确实是最好的语言(手动狗头哈哈),写起来真的很舒爽,没有任何心智负担,字符串和整型压根就不用区分,开发速度真的是比GO快很多。现在工作中也还是有一些老项目在使用PHP,但21年之后的新项目基本上就都是用GO了。那为什么PHP那么香,还要转战使用GO呢,下面就给大家讲解一下我们新项目从PHP转GO的原因,有几个比较重要的点:

1、PHP不能满足我们的高并发业务,这是最主要的原因了,(PS:我这里所说的PHP是指官方的php-fpm模式下的开发,是一个请求一个进程的那种模式,而不是类似于swoole常驻进程的那种。那么为什么不去使用swoole呢,当然也是有的,但swoole毕竟太小众了,且之前有很多bug,使用起来心智负担太高了),而我们部门所负责的是直播业务,每天都和高并发打交道啊,所以只能将目光转向了并发小王子GO的怀抱。

2、GO语言当时在市面上很火,像腾讯、百度、滴滴、好未来这些大厂都在陆陆续续地从PHP转向GO,这也是一个讯号吧,跟着大佬们走总不会错。

3、GO语言的简单简洁,相比较于JAVA,上手是很快的(但真正学好还是没那么容易的),我当时就学了两个礼拜左右语法就跟着一起写项目了。

二、GO解决的并发问题

说到并发,是GO最基本的功能了,但是在传统的PHP中是比较困难的,如果不借助其它一些扩展的话,是做不到并发的。举个场景:每个用户进入直播间,都要获取很多信息,有版本服务信息、直播基础信息、用户信息、直播关联权益信息、直播间信息统计等等。如果是PHP的写法,就得按照下面串行的流程去做,这个接口耗时就是所有操作的时间之和,严重影响用户体验啊。

但如果换成GO去做这件事,那就非常清爽了,这个用户请求耗时就只需要时间最长的那个操作耗时,如下图:

那么我们如何用去实现这个并发逻辑呢?

方法1:使用sync.WaitGroup

//请求入口
func main() {var (VersionDetail, LiveDetail, UserDetail, EquityDetail, StatisticsDetail int)ctx := context.Background()GoNoErr(ctx, func() {VersionDetail = 1 //版本服务信息time.Sleep(1 * time.Second)fmt.Println("执行第一个任务")}, func() {LiveDetail = 2 //直播基础信息time.Sleep(2 * time.Second)fmt.Println("执行第二个任务")}, func() {UserDetail = 3 //用户信息time.Sleep(3 * time.Second)fmt.Println("执行第三个任务")}, func() {EquityDetail = 4 //直播关联权益信息time.Sleep(4 * time.Second)fmt.Println("执行第四个任务")}, func() {StatisticsDetail = 5 //直播间信息统计time.Sleep(5 * time.Second)fmt.Println("执行第五个任务")})fmt.Println(VersionDetail, LiveDetail, UserDetail, EquityDetail, StatisticsDetail)
}//并发方法
func GoNoErr(ctx context.Context, functions ...func()) {var wg sync.WaitGroupfor _, f := range functions {wg.Add(1)// 每个函数启动一个协程go func(function func()) {function()wg.Done()}(f)}// 等待执行完wg.Wait()
}

方法2:使用ErrGroup库

//请求入口
func main() {var (VersionDetail, LiveDetail, UserDetail, EquityDetail, StatisticsDetail interr                                                                   error)ctx := context.Background()err = GoErr(ctx, func() error {VersionDetail = 1 //版本服务信息time.Sleep(1 * time.Second)fmt.Println("执行第一个任务")return nil //返回实际执行的错误}, func() error {LiveDetail = 2 //直播基础信息time.Sleep(2 * time.Second)fmt.Println("执行第二个任务")return nil //返回实际执行的错误}, func() error {UserDetail = 3 //用户信息time.Sleep(3 * time.Second)fmt.Println("执行第三个任务")return nil //返回实际执行的错误}, func() error {EquityDetail = 4 //直播关联权益信息time.Sleep(4 * time.Second)fmt.Println("执行第四个任务")return nil //返回实际执行的错误}, func() error {StatisticsDetail = 5 //直播间信息统计time.Sleep(5 * time.Second)fmt.Println("执行第五个任务")return nil //返回实际执行的错误})if err != nil {fmt.Println(err)return}fmt.Println(VersionDetail, LiveDetail, UserDetail, EquityDetail, StatisticsDetail)}func GoErr(ctx context.Context, functions ...func() error) error {var eg errgroup.Groupfor i := range functions { f := functions[i]  //请注意这里的写法,下面有讲解eg.Go(func() (err error) {err = f()if err != nil {//记日志}return err})}// 等待执行完return eg.Wait()
}

上面就是使用ErrGroup库的并发执行任务的方法,可以直接拿来使用,ErrGroup这是GO官方提供的一个同步扩展库可以很好地将⼀个通⽤的⽗任务拆成⼏个⼩任务并发执⾏

上面有一点需要特别注意的写法,就是下面这段代码的写法,写法1:

for i := range functions { f := functions[i]  eg.Go(func() (err error) {err = f()

也可以这样写,写法2:

for _, f := range functions { fs := f  eg.Go(func() (err error) {err = fs()

但如果这样写就会有问题,写法3:

for _, f := range functions { eg.Go(func() (err error) {err = f()

你们可以改一下,实际跑一下。会发现 (写法3) 会出现类似这样的错误结果

正确预期的结果(写法1、写法2)应该是这样的

这是因为在 Go 语言中,当使用闭包(匿名函数)时,如果闭包引用了外部的变量,闭包实际上会捕获这些变量的引用。在循环中创建闭包时,如果直接将循环变量作为闭包的参数或在闭包中引用该变量,会导致所有生成的闭包都引用相同的变量,即最后一次迭代的值。

为了避免这个问题,常见的做法是在循环内部创建一个新的变量,将循环变量的值赋给这个新变量,然后在闭包中引用该新变量。这样,每次循环迭代都会创建一个新的变量,闭包捕获的是不同的变量引用,而不是相同变量的引用。

在给定的代码中,fs := f 就是为了创建一个新的变量 f,并将循环变量 f 的值赋给它。这样,在闭包中就可以安全地引用这个新变量 f,而不会受到循环迭代的影响。这个技巧非常有用,可以在循环中创建多个独立的闭包,并确保它们捕获的是预期的变量值,而不会受到循环迭代的干扰

当然,还有一些第三方库也实现了上面的并发分组操作,大家感兴趣的可以去GitHub上看看,但功能和实现基本都大同小异。以上就是GO并发的基础,将一个父任务拆分成多个子任务去执行,提高程序的并发度,节省程序耗时。我们平时在工作中,两种方法都可以直接拿来使用,可以说这两个GO并发方法几乎贯穿了我的GO职业生涯,也是最基础最实用的并发操作方法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/708984.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

线程安全使用 HashMap 的四种技巧

这篇文章,我们聊聊线程安全使用 HashMap 的四种技巧。1方法内部:每个线程使用单独的 HashMap 如下图,tomcat 接收到到请求后,依次调用控制器 Controller、服务层 Service 、数据库访问层的相关方法。 每次访问服务层方法 serviceMethod 时,都会在方法体内部创建一个单独的…

C - AtCoder Magics

C - AtCoder Magics https://atcoder.jp/contests/abc354/tasks/abc354_c思路 首先按照a属性对数列进行排序,大的在前,小的在后, 完成后, 则数列在a参数上是非递增的。 如下图中x轴对应 a 参数, y轴对应c参数, discard条件,实际上是找出 数列 对于c参数 沿着 a 参数非递…

『手撕Vue-CLI』添加自定义指令

前言 经上篇『手撕Vue-CLI』添加帮助和版本号的介绍之后,已经可以在控制台中输入 nue --help 来查看帮助信息了,但是在帮助信息中只有 --version,--help 这两个指令,而 vue-cli 中还有很多指令,例如 create,serve,build 等等,所以本章将继续添加自定义指令,例如 creat…

Web入门

SQL注入 数据库基础 *关系型数据库: #Access#MSSQL -- 1433*非关系型数据库: #MySQL -- 3306#Oracle -- 1521等对MySQL数据库的操作 1.显示数据库 show databases;2.显示数据库版本 select version(); 3.使用数据库 use XXX;4.显示当前正在使用的数据库 selec…

Linux常用命令-文件目录命令

Linux常用命令-文件目录命令1.目录命令 1.1、ls命令:显示目录下的内容 基本格式 [root@localhost ~]# ls [选项] [参数是文件名或目录名]常用选项 -a:显示所有文件。 --color=when。支持颜色输出,when的值默认是always(总显示颜色),never(不显示颜色)和auto(自动)。 -d:显…

asdf

asf本文版权归作者和博客园共有,欢迎转载,转载请注明原文链接:https://www.cnblogs.com/lllliuxiaoxia/p/18200863另外欢迎关注公众号,一起讨论学习

啊手动阀手动阀

阿斯顿发射点发射点发生发射点发撒打发本文来自博客园,作者:胖树,转载请注明原文链接:https://www.cnblogs.com/lllliuxiaoxia/p/18200857关注我的公众号不定期推送资讯

配置SQLServer远程连接

要在 SQL Server 上启用远程连接,需要执行以下步骤:1、确保 SQL Server 已启用远程连接:  登录到 SQL Server 所在的计算机上。  打开 SQL Server Management Studio (SSMS)。  使用 Windows 身份验证或 SQL Server 身份验证登录 SQL Server。  在左侧的对象资源管理…

uCTRL论文阅读笔记

uCTRL: Unbiased Contrastive Representation Learning via Alignment and Uniformity for Collaborative Filtering论文阅读笔记 这篇文章应该是关于无偏推荐的 Abstract ​ 由于协作过滤(CF)模型的隐式用户反馈偏向于流行的项目,CF模型倾向于产生带有流行偏差的推荐列表。…

SQL Server 2012提供了多种备份和还原数据库的方法,包括以下几种:

SQL Server 2012提供了多种备份和还原数据库的方法,包括以下几种:SQL Server Management Studio(SSMS):SSMS是一个支持图形用户界面的工具,可以通过它备份和还原整个数据库或特定的数据表、视图等。在SSMS中,可以通过右键单击数据库并选择“任务” > “备份”或“还原…

BUUCTF-WEB(15-20)

[极客大挑战 2019]BabySQL 打开题目就试了试万能密码,是不行的推测应该做了过滤 我们密码框输入1 1 or 1=1试试发现我们的or没了,union,select,where都被过滤了,应该是被替换成空字符,所以我们可以双写绕过 uunionnion sselectelect wwherehere由于这里or被过滤了,我们无…