极端情况下收缩 Go 进程的线程数

时间:2022-07-25
本文章向大家介绍极端情况下收缩 Go 进程的线程数,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

在 Go 的 runtime 里有一些创建了就没法回收的东西。

之前在 这篇 里讲过 allgs 没法回收的问题。

除了 allgs 之外,当前 Go 创建的线程也是没法退出的,比如这个来自 xiaorui.cc 的例子,我简单做了个修改,能从网页看到线程:

package main


/*
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void output(char *str) {
    usleep(1000000);
    printf("%sn", str);
}
*/
import "C"
import "unsafe"


import "net/http"
import _ "net/http/pprof"


func init() {
 go http.ListenAndServe(":9999", nil)
}


func main() {
    for i := 0;i < 1000;i++ {
        go func(){
            str := "hello cgo"
            //change to char*
            cstr := C.CString(str)
            C.output(cstr)
            C.free(unsafe.Pointer(cstr))


        }()
    }
    select{}
}

可见 Goroutine 退出了,历史上创建的线程也是不会退出的。之前我也一直认为没有办法退出这些线程,不过这周被同事教育,还是有办法的。参考官方 issue 14592。文末有链接。

虽然问题直到现在依然没解决,但是这个 issue 里也提供了一种邪道解决办法,直接调用 LockOSThread,而不调用 Unlock,这样在退出的时候和当前 g 绑定的线程就会直接销毁:

把开头的程序改改,增加一个接口,killThread。

package main


/*
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void output(char *str) {
    usleep(1000000);
    printf("%sn", str);
}
*/
import "C"


import (
 "net/http"
 "unsafe"


 "log"
 _ "net/http/pprof"
 "runtime"
 "sync"
)


func init() {
 go http.ListenAndServe(":9999", nil)
}


func main() {
 for i := 0; i < 1000; i++ {
  go func() {
   str := "hello cgo"
   //change to char*
   cstr := C.CString(str)
   C.output(cstr)
   C.free(unsafe.Pointer(cstr))


  }()
 }
 killThreadService()
 select {}
}


func sayhello(wr http.ResponseWriter, r *http.Request) {
 KillOne()
}


func killThreadService() {
 http.HandleFunc("/", sayhello)
 err := http.ListenAndServe(":10003", nil)
 if err != nil {
  log.Fatal("ListenAndServe:", err)
 }
}


// KillOne kills a thread
func KillOne() {
 var wg sync.WaitGroup
 wg.Add(1)


 go func() {
  defer wg.Done()
  runtime.LockOSThread()
  return
 }()


 wg.Wait()
}


启动后,发现创建了 1k+ 线程,curl localhost:10003,可以发现线程数在逐渐降低。

[1] https://github.com/golang/go/issues/14592