Redian新闻
>
go高并发之路—go语言如何解决并发问题

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

//请求入口
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.WaitGroup
for _, 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 int
err 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.Group
for 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职业生涯,也是最基础最实用的并发操作方法

一个人可以被毁灭,但不可以被打败。

链接:https://www.cnblogs.com/lmz-blogs/p/18200946

(版权归原作者所有,侵删)


微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
一文精通GO的并发与并行清北爸爸辅导数学崩溃瞬间,这个国产大模型有解!AI启发问答关键情绪稳定豆包大模型价格出炉!超高并发,“后付费”支持每分钟万次请求长篇小说《谷雨立夏间》82 立夏的夏天Go语言之父反思错误:“并发”理念指导不足、没做好包管理、用C写了编译器、文档缺失……蒲松龄自画像:“青衫白帢久飘零”美国移民局发布H-1B申请获签者画像,中国人中签率走低,如何解决?账上趴着超60亿元现金,百亿市值钢企两年零分红,上交所发问:有大额资金闲置?亚历山大、汉武帝、成吉思汗远征时,是如何解决后勤补给问题的如何解决电池爆炸的问题?科学家:用水做电池ESG投资回报高?如何解决小微企业融资难问题?屠光绍、孙小系等热议…前马耳他身份局首席法律顾问——Gouder博士到访海那边,详解马耳他永居走自己的路——中俄欧亚安全合作新构想灵魂发问:钱去哪了?澳洲女生灵魂发问:一年挣18万,但我根本做不起头发亚马逊等巨头暂停PERM,裁员潮下的绿卡困境该如何解决?英国语言班即将开启,短期语言班住哪里?快来看看语言班同学都住哪!德国马克思城堡(Marksburg castle),街头看景2023年后院里看见的野生动物为抢一块地邻里争了30年!青丝变白发之后,终于靠3年官司解决了!品牌周报|大人糖探索女性公共友好空间的新可能、likeuu 3周年向青春期发问、HBN续写「求真」的故事《尘封档案》拾遗之059:“乞丐命案”的背后鲜为人知!Costco高级会员资格竟有4大缺点人类寿命极限只有120岁?诺奖得主发问:为何我们非得死?AP英语语言如何顺利获得5分?社长推荐|《十大经济学家:繁荣之路与奴役之路》销赃问题难解决是“因为有市场”!波士顿华埠召开治安会 检察官谈盗窃与流浪问题[单车] 电瓜——GRAVEL 瓜车 最佳答案真诚发问,这种混乱性行为正常吗英国语言班开启,快来看看12周语言、8周语言班的同学住哪里!【白皮书专题五】I-20失效滞留美国怎么办?如何解决?全英语言班即将开启,快来看看这些便宜的语言班房源吧,寻找一个语言班搭子!GO的并发与并行加拿大Costco高薪招人! 网友:又在画大饼?5名游客被绑架勒索近50万! 海外安全问题何解?
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。