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

2020-01-28 12:58:45王振洲

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

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

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

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


type Semaphore struct {
 Threads chan int
 Wg  sync.WaitGroup
}

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


func (sem *Semaphore) P() {
 sem.Threads <- 1
 sem.Wg.Add(1)
}

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


func (sem *Semaphore) V() {
 sem.Wg.Done()
 <-sem.Threads
}

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


func (sem *Semaphore) Wait() {
 sem.Wg.Wait()
}

完整代码可以在 Github: semaphore 中查看

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

怎么让goroutine主动退出

对于goroutine的主动退出,比较友好的做法就是循环监听一个channel,通过类似信号的方式来告知goroutine的”该退出了“,然后goroutine自己主动退出,这种做法在网上十分常见,也是Golang官方推荐的做法,思想也很简单。


func main() {
 ok, quit := make(chan int, 1), make(chan int, 1)
 go func() {
  i := 0
  for {
   select {
   case <-quit:
    ok <- 1
    return
   default:
    HeavyWork(i)
    i++
   }
  }
 }()
 time.Sleep(5 * time.Second)
 quit <- 1
 <-ok
}

运行结果如下图

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

上面讲了一些关于goroutines和channel的简单使用,接下来终于写到本文的重点了。笔者并没有解决如何从外部杀死一个goroutine,但记录了尝试“杀死”中的可行或不可行方法,希望对各位有所帮助。

因为近期在开发中遇到这样一个问题,当一个函数是极其冗长、复杂度高、难以解耦的顺序结构代码时(例如某个极其复杂无循环结构的加密算法),而且由于数据量巨大,需要反复调用该函数,由于每运行一次,程序都会消耗大量的时间、空间,那么当一个任务已经被用户抛弃时,如何才能抛弃仍在做着无用功的goroutine?