Go
语言中,goroutine
的创建成本很低,调度效率很高,人称可以开几百几千万个goroutine
,但是真正开几百几千万个goroutine
就不会有任何影响吗?本文我们就一起来看一看goroutine
是否有数量限制并介绍几种正确使用goroutine
的姿势~。
在Go
语言中,goroutine
的创建成本很低,调度效率高,Go
语言在设计时就是按以数万个goroutine
为规范进行设计的,数十万个并不意外,但是goroutine
在内存占用方面确实具有有限的成本,你不能创造无限数量的它们,比如这个例子:
ch := generate()
go func() {
for range ch { }
}()
这段代码通过generate()
方法获得一个channel
,然后启动一个goroutine
一直去处理这个channel
的数据,这个goroutine
什么时候会退出?答案是不确定,ch
是由函数generate()
来决定的,所以有可能这个goroutine
永远都不会退出,这就有可能会引发内存泄漏。
goroutine
就是G-P-M
调度模型中的G
,我们可以把goroutine
看成是一种协程,创建goroutine
也是有开销的,但是开销很小,初始只需要2-4k
的栈空间,当goroutine
数量越来越大时,同时存在的goroutine
也越来越多时,程序就隐藏内存泄漏的问题。看一个例子:
func main() {
for i := 0; i < math.MaxInt64; i++ {
go func(i int) {
time.Sleep(5 * time.Second)
}(i)
}
}
大家可以在自己的电脑上运行一下这个程序,观察一下CPU
和内存占用情况,我说下我运行后的现象:
因此每次在编写GO
程序时,都应该仔细考虑一个问题:
您将要启动的
goroutine
将如何以及在什么条件下结束?
接下来我们就来介绍几种方式可以控制goroutine
和goroutine
的数量。
goroutine
的方法Context
包Go 语言中的每一个请求的都是通过一个单独的 goroutine
进行处理的,HTTP/RPC
请求的处理器往往都会启动新的Goroutine
访问数据库和 RPC
服务,我们可能会创建多个goroutine
来处理一次请求,而 Context
的主要作用就是在不同的 goroutine
之间同步请求特定的数据、取消信号以及处理请求的截止日期。
Context
包主要衍生了四个函数:
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context
使用这四个函数我们对goroutine
进行控制,具体展开就不再本文说了,我们以WithCancel
方法写一个例子:
func main() {
ctx,cancel := context.WithCancel(context.Background())
go Speak(ctx)
time.Sleep(10*time.Second)
cancel()
time.Sleep(2 * time.Second)
fmt.Println("bye bye!")
}
func Speak(ctx context.Context) {
for range time.Tick(time.Second){
select {
case <- ctx.Done():
fmt.Println("asong哥,我收到信号了,要走了,拜拜!")
return
default:
fmt.Println("asong哥,你好帅呀~balabalabalabala")
}
}
}
运行结果:
asong哥,你好帅呀~balabalabalabala
# ....... 省略部分
asong哥,我收到信号了,要走了,拜拜!
bye bye!
这里我们使用withCancel
创建了一个基于Background
的ctx
,然后启动了一个goroutine
每隔1s
夸我一句,10s
后在主goroutine
中发送取消新信号,那么启动的goroutine
在检测到信号后就会取消退出。
channel
我们知道channel
是用于goroutine
的数据通信,在Go
中通过goroutine+channel
的方式,可以简单、高效地解决并发问题。上面我们介绍了使用context
来达到对goroutine
的控制,实际上context
的内部实现也是使用的channel
,所以有时候为了实现方便,我们可以直接通过channel+select
或者channel+close
的方式来控制goroutine
的退出,我们分别来一写一个例子:
channel+select
func fibonacci(ch chan int, done chan struct{}) {
x, y := 0, 1
for {
select {
case ch <- x:
x, y = y, x+y
case <-done:
fmt.Println("over")
return
}
}
}
func main() {
ch := make(chan int)
done := make(chan struct{})
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-ch)
}
done <- struct{}{}
}()
fibonacci(ch, done)
}
上面的例子是计算斐波那契数列的结果,我们使用两个channel
,一个channel
用来传输数据,另外一个channel
用来做结束信号,这里我们使用的是select
的阻塞式的收发操作,直到有一个channel
发生状态改变,我们也可以在select
中使用default
语句,那么select
语句在执行时会遇到这两种情况:
Channel
时,直接处理该Channel
对应的 case
;Channel
时,执行 default
中的语句;建议大家使用带default
的方式,因为在一个nil channel
上的操作会一直被阻塞,如果没有default case
,只有nil channel
的select
会一直被阻塞。
channel+close
channel
可以单个出队,也可以循环出队,因为我们可以使用for-range
循环处理channel
,range ch
会一直迭代到channel
被关闭,根据这个特性,我们也可做到对goroutine
的控制:
func main() {
ch := make(chan int, 10)
go func() {
for i:=0; i<10;i++{
ch <- i
}
close(ch)
}()
go func() {
for val := range ch{
fmt.Println(val)
}
fmt.Println("receive data over")
}()
time.Sleep(5* time.Second)
fmt.Println("program over")
}
如果对channel
不熟悉的朋友可以看一下我之前的文章:[学习channel设计:从入门到放弃]
goroutine
的数量我们可以通过以下方式达到控制goroutine
数量的目的,不过本身Go
的goroutine
就已经很轻量了,所以控制goroutine
的数量还是要根据具体场景分析,并不是所有场景都需要控制goroutine
的数量的,一般在并发场景我们会考虑控制goroutine
的数量,接下来我们来看一看如下几种方式达到控制goroutine
数量的目的。
写 go
并发程序的时候如果程序会启动大量的goroutine
,势必会消耗大量的系统资源(内存,CPU),所以可以考虑使用goroutine
池达到复用goroutine
,节省资源,提升性能。也有一些开源的协程池库,例如:ants
、go-playground/pool
、jeffail/tunny
等,这里我们看ants
的一个官方例子:
var sum int32
func myFunc(i interface{}) {
n := i.(int32)
atomic.AddInt32(&sum, n)
fmt.Printf("run with %d\n", n)
}
func demoFunc() {
time.Sleep(10 * time.Millisecond)
fmt.Println("Hello World!")
}
func main() {
defer ants.Release()
runTimes := 1000
// Use the common pool.
var wg sync.WaitGroup
syncCalculateSum := func() {
demoFunc()
wg.Done()
}
for i := 0; i < runTimes; i++ {
wg.Add(1)
_ = ants.Submit(syncCalculateSum)
}
wg.Wait()
fmt.Printf("running goroutines: %d\n", ants.Running())
fmt.Printf("finish all tasks.\n")
// Use the pool with a function,
// set 10 to the capacity of goroutine pool and 1 second for expired duration.
p, _ := ants.NewPoolWithFunc(10, func(i interface{}) {
myFunc(i)
wg.Done()
})
defer p.Release()
// Submit tasks one by one.
for i := 0; i < runTimes; i++ {
wg.Add(1)
_ = p.Invoke(int32(i))
}
wg.Wait()
fmt.Printf("running goroutines: %d\n", p.Running())
fmt.Printf("finish all tasks, result is %d\n", sum)
}
这个例子其实就是计算大量整数和的程序,这里通过ants.NewPoolWithFunc()
创建了一个 goroutine
池。第一个参数是池容量,即池中最多有 10
个goroutine
。第二个参数为每次执行任务的函数。当我们调用p.Invoke(data)
的时候,ants
池会在其管理的 goroutine
中找出一个空闲的,让它执行函数taskFunc
,并将data
作为参数。
具体这个库的设计就不详细展开了,后面会专门写一篇文章来介绍如何设计一个协程池。
Semaphore
Go
语言的官方扩展包为我们提供了一个基于权重的信号量Semaphore,我可以根据信号量来控制一定数量的 goroutine
并发工作,官方也给提供了一个例子:workerPool,代码有点长就不在这里贴了,我们来自己写一个稍微简单点的例子:
const (
Limit = 3 // 同时运行的goroutine上限
Weight = 1 // 信号量的权重
)
func main() {
names := []string{
"asong1",
"asong2",
"asong3",
"asong4",
"asong5",
"asong6",
"asong7",
}
sem := semaphore.NewWeighted(Limit)
var w sync.WaitGroup
for _, name := range names {
w.Add(1)
go func(name string) {
sem.Acquire(context.Background(), Weight)
fmt.Println(name)
time.Sleep(2 * time.Second) // 延时能更好的体现出来控制
sem.Release(Weight)
w.Done()
}(name)
}
w.Wait()
fmt.Println("over--------")
}
上面的例子我们使用 NewWeighted()
函数创建一个并发访问的最大资源数,也就是同时运行的goroutine
上限为3
,使用Acquire
函数来获取指定个数的资源,如果当前没有空闲资源可用,则当前goroutine
将陷入休眠状态,最后使用release
函数释放已使用资源数量(计数器)进行更新减少,并通知其它 waiters
。
channel+waitgroup
实现这个方法我是在煎鱼大佬的一篇文章学到的:来,控制一下Goroutine的并发数量
主要实现原理是利用waitGroup
做并发控制,利用channel
可以在goroutine
之间进行数据通信,通过限制channel
的队列长度来控制同时运行的goroutine
数量,例子如下:
func main() {
count := 9 // 要运行的goroutine数量
limit := 3 // 同时运行的goroutine为3个
ch := make(chan bool, limit)
wg := sync.WaitGroup{}
wg.Add(count)
for i:=0; i < count; i++{
go func(num int) {
defer wg.Done()
ch <- true // 发送信号
fmt.Printf("%d 我在干活 at time %d\n",num,time.Now().Unix())
time.Sleep(2 * time.Second)
<- ch // 接收数据代表退出了
}(i)
}
wg.Wait()
}
这种实现方式真的妙,与信号量的实现方式基本相似,某些场景大家也可以考虑使用这种方式来达到控制goroutine
的目的,不过最好封装一下,要不有点丑陋
本文主要目的是介绍控制goroutine
的几种方式、控制goroutine
数量的几种方式,goroutine
的创建成本低、效率高带来了很大优势,同时也会有一些弊端,这就需要我们在实际开发中根据具体场景选择正确的方式使用goroutine
,本文介绍的技术方案也可能是片面的,如果你有更好的方式可以在评论区中分享出来,我们大家一起学习学习~。
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/JC14dWffHub0nfPlPipsHQ
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。