ToB企服应用市场:ToB评测及商务社交产业平台

标题: go高并发之路——go语言如何解决并发题目 [打印本页]

作者: 道家人    时间: 2024-5-20 21:31
标题: go高并发之路——go语言如何解决并发题目
一、选择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
  1. //请求入口
  2. func main() {
  3.         var (
  4.                 VersionDetail, LiveDetail, UserDetail, EquityDetail, StatisticsDetail int
  5.         )
  6.         ctx := context.Background()
  7.         GoNoErr(ctx, func() {
  8.                 VersionDetail = 1 //版本服务信息
  9.                 time.Sleep(1 * time.Second)
  10.                 fmt.Println("执行第一个任务")
  11.         }, func() {
  12.                 LiveDetail = 2 //直播基础信息
  13.                 time.Sleep(2 * time.Second)
  14.                 fmt.Println("执行第二个任务")
  15.         }, func() {
  16.                 UserDetail = 3 //用户信息
  17.                 time.Sleep(3 * time.Second)
  18.                 fmt.Println("执行第三个任务")
  19.         }, func() {
  20.                 EquityDetail = 4 //直播关联权益信息
  21.                 time.Sleep(4 * time.Second)
  22.                 fmt.Println("执行第四个任务")
  23.         }, func() {
  24.                 StatisticsDetail = 5 //直播间信息统计
  25.                 time.Sleep(5 * time.Second)
  26.                 fmt.Println("执行第五个任务")
  27.         })
  28.         fmt.Println(VersionDetail, LiveDetail, UserDetail, EquityDetail, StatisticsDetail)
  29. }
  30. //并发方法
  31. func GoNoErr(ctx context.Context, functions ...func()) {
  32.         var wg sync.WaitGroup
  33.         for _, f := range functions {
  34.                 wg.Add(1)
  35.                 // 每个函数启动一个协程
  36.                 go func(function func()) {
  37.                         function()
  38.                         wg.Done()
  39.                 }(f)
  40.         }
  41.         // 等待执行完
  42.         wg.Wait()
  43. }
复制代码
方法2:使用ErrGroup库
  1. //请求入口
  2. func main() {
  3.         var (
  4.                 VersionDetail, LiveDetail, UserDetail, EquityDetail, StatisticsDetail int
  5.                 err                                                                   error
  6.         )
  7.         ctx := context.Background()
  8.         err = GoErr(ctx, func() error {
  9.                 VersionDetail = 1 //版本服务信息
  10.                 time.Sleep(1 * time.Second)
  11.                 fmt.Println("执行第一个任务")
  12.                 return nil //返回实际执行的错误
  13.         }, func() error {
  14.                 LiveDetail = 2 //直播基础信息
  15.                 time.Sleep(2 * time.Second)
  16.                 fmt.Println("执行第二个任务")
  17.                 return nil //返回实际执行的错误
  18.         }, func() error {
  19.                 UserDetail = 3 //用户信息
  20.                 time.Sleep(3 * time.Second)
  21.                 fmt.Println("执行第三个任务")
  22.                 return nil //返回实际执行的错误
  23.         }, func() error {
  24.                 EquityDetail = 4 //直播关联权益信息
  25.                 time.Sleep(4 * time.Second)
  26.                 fmt.Println("执行第四个任务")
  27.                 return nil //返回实际执行的错误
  28.         }, func() error {
  29.                 StatisticsDetail = 5 //直播间信息统计
  30.                 time.Sleep(5 * time.Second)
  31.                 fmt.Println("执行第五个任务")
  32.                 return nil //返回实际执行的错误
  33.         })
  34.         if err != nil {
  35.                 fmt.Println(err)
  36.                 return
  37.         }
  38.         fmt.Println(VersionDetail, LiveDetail, UserDetail, EquityDetail, StatisticsDetail)
  39. }
  40. func GoErr(ctx context.Context, functions ...func() error) error {
  41.         var eg errgroup.Group
  42.         for i := range functions {
  43.                 f := functions[i]  //请注意这里的写法,下面有讲解
  44.                 eg.Go(func() (err error) {
  45.                         err = f()
  46.                         if err != nil {
  47.                                 //记日志
  48.                         }
  49.                         return err
  50.                 })
  51.         }
  52.         // 等待执行完
  53.         return eg.Wait()
  54. }
复制代码
上面就是使用ErrGroup库的并发执行任务的方法,可以直接拿来使用,ErrGroup这是GO官方提供的一个同步扩展库可以很好地将⼀个通⽤的⽗任务拆成⼏个⼩任务并发执⾏
上面有一点需要特别注意的写法,就是下面这段代码的写法,写法1:
  1. for i := range functions {
  2.                 f := functions[i]  
  3.                 eg.Go(func() (err error) {
  4.                         err = f()
复制代码
也可以这样写,写法2:
  1. for _, f := range functions {
  2.                 fs := f  
  3.                 eg.Go(func() (err error) {
  4.                         err = fs()
复制代码
但如果这样写就会有题目,写法3:
  1. for _, f := range functions {
  2.                 eg.Go(func() (err error) {
  3.                         err = f()
复制代码
你们可以改一下,现实跑一下。会发现 (写法3) 会出现类似这样的错误结果

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

这是因为在 Go 语言中,当使用闭包(匿名函数)时,如果闭包引用了外部的变量,闭包现实上会捕获这些变量的引用。在循环中创建闭包时,如果直接将循环变量作为闭包的参数或在闭包中引用该变量,会导致所有天生的闭包都引用相同的变量,即最后一次迭代的值。
为了避免这个题目,常见的做法是在循环内部创建一个新的变量,将循环变量的值赋给这个新变量,然后在闭包中引用该新变量。这样,每次循环迭代都会创建一个新的变量,闭包捕获的是不同的变量引用,而不是相同变量的引用。
在给定的代码中,fs := f 就是为了创建一个新的变量 f,并将循环变量 f 的值赋给它。这样,在闭包中就可以安全地引用这个新变量 f,而不会受到循环迭代的影响。这个本领非常有用,可以在循环中创建多个独立的闭包,并确保它们捕获的是预期的变量值,而不会受到循环迭代的干扰
当然,还有一些第三方库也实现了上面的并发分组利用,大家感爱好的可以去GitHub上看看,但功能和实现基本都大同小异。以上就是GO并发的底子,将一个父任务拆分成多个子任务去执行,提高程序的并发度,节省程序耗时。我们平时在工作中,两种方法都可以直接拿来使用,可以说这两个GO并发方法几乎贯穿了我的GO职业生活,也是最底子最实用的并发利用方法

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4