Golang探索对Goroutine的控制方法(详解)

  

  

在golang中,只需要在函数调用前加上关键字去即可创建一个并发任务单元,而这个新建的任务会被放入队列中,等待调度器安排。相比系统的m级别线程栈,goroutine的自定义栈只有2 kb,这使得我们能够轻易创建上万个并发任务,如此对性能提升不少。但随之而来的有以下几个问题:

  

如何等待所有goroutine的退出

  

如何限制创建goroutine的数量(信号量实现)

  

怎么让goroutine主动退出

  

探索——如何从外部杀死goroutine

  

本文记录了笔者就以上几个问题进行探究的过程,文中给出了大部分问题的解决方案,同时也抛出了未解决的问题,期待与各位交流:p

  

  

开始之前先定义一个常量const N=100以及一个HeavyWork函数,假定该函数具有极其冗长,复杂度高,难以解耦的特性

        func HeavyWork (id int) {   rand.Seed (int64 (id))   间隔:=time.Duration (rand.Intn (3) + 1) * time.Second   time . sleep(间隔)   fmt。Printf (" HeavyWork % 3 d成本% v \ n”, id,间隔)   }      

以上定义的内容将在之后的代码中直接使用以缩减篇幅、大部分完整代码可在Github: explore-goroutine中找到

  

  

"不通过共享内存进行通信;相反,共享内存通信”——的一大设计哲学《共享内存的通信》

  

翻译成中文就是,用通信来共享内存数据,而不要通过共享内存数据来进行通信。

  

中去的了goroutine和渠道提供了一种优雅而独特的结构化并发软件的方法,我们可以利用通道(通道)的特性,来实现当前等待goroutine的操作。但是通道并不是当前这个场景的最佳方案,用它来实现的方式是稍显笨拙的,需要知道确定个数的goroutine,同时稍不注意就极易产生死锁,代码如下:

     //代码“说说很简单,给我。”   函数main () {   陈waitChan:=(int, 1)   我:=0;我& lt;N;我+ + {   去func (n int) {   HeavyWork (n)   waitChan & lt; - 1   }(我)   }   问:=0   用于范围waitChan {   问+ +   如果问==N {   打破   }   }   关上(waitChan)   fmt.Println(“完成”)   }      

上述代码使用了一个缓存大小为1的通道(频道),创建N个goroutine用于运行HeavyWork,每个任务完成后向waitChan写入一个数据,在收到N个完成信号后退出。

  

但事实上比较优雅的方式是使用去标准库同步,其中提供了专门的解决方案sync.WaitGroup用于等待一个了goroutine集合的结束

     //代码“说说很简单,给我。”   函数main () {   wg:=sync.WaitGroup {}   我:=0;我& lt;N;我+ + {   wg.Add (1)   去func (n int) {   推迟wg.Done ()   HeavyWork (n)   }(我)   }   wg.Wait ()   fmt.Println(“完成”)   }      

关于同步。WaitGroup的具体使用请参照官方文档(GoDoc)同步。WaitGroup,这里不再赘述

  

  

信号量(信号量),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。

  

其中V操作会增加信号量的数值即释放资源,而p操作会减少它即占用资源

  

那么非常容易想到的就是利用通道(通道)缓存有限的特性,它允许我们可以自实现一个简单的数量控制,就如同使用信号量一般,在这基础再加上前面提到的sync.WaitGroup,我们可以打出一套组合拳,提供可阻塞的信号量PV操作,能够实现固定创建goroutine数量并且支持等待当前goroutine的退出。结构体定义如下:

        型信号量结构{   线程陈int   Wg sync.WaitGroup   }      

而p操作只需在通道中加入一个元素同时调用WaitGroup.Add即可,这一操作完成对资源的申请

        func (sem *信号量)P () {   扫描电镜。线程& lt; - 1   sem.Wg.Add (1)   }      

相反则是V操作,进行资源的释放

        func (sem *信号量)V () {   sem.Wg.Done ()   & lt; -sem.Threads   }      

等则阻塞等待直到当前所有资源都归还,直接调用WaitGroup的方法即可

        func (sem *信号)等(){   sem.Wg.Wait ()   }      

完整代码可以在Github:信号量中查看

  

利用上面的信号量就可以做的到,在一个时刻的了goroutine数量不会超过信号量值的大小,而某个goroutine退出后将返还占用的信号量,而正在等待的goroutine就可以立即申请,下图形象地展现了运行时的状态

Golang探索对Goroutine的控制方法(详解)