协程独占 CPU 导致其他协程饿死
协程是协作式抢占调度,协程本身不会主动让出CPU:
func main() {
// 防止主线程退出
defer func(){
for { } // 这句会导致 cpu 占用 100%
}()
}
解决办法是通过阻塞的方式来避免 CPU 占用:
func main() {
// 防止主线程退出
defer func(){
select { }
}()
}
特别说明:
使用 for {} 或 select{} 来进行阻塞 main() 程序,否则可能造成 goroutine 泄漏。
防止 mian() 程序中 http 服务器挂壁,应该找出错误捕获错误,阻塞是治标不治本,并且还会造成其他问题,比如定时器无法退出。
使用 runtime.Gosched() 让出 CPU 时间片
// 定义个函数
func test (s string) {
for i := 0; i < 2; i++ {
fmt.Println(s)
}
}
同步调用:
func main() {
test("1")
test("2")
}
// 输出结果:
// 1
// 1
// 2
// 2
协程调用:
func main() {
go test("1")
test("2")
}
// 输出结果:
// 2
// 2
test("2")time.Sleep()test("1")runtime.GOMAXPROCS(1)
如何不使用 time.sleep() 来达到输出两个 1 和两个 2 ?
手动让出时间片:
func test (s string) {
for i := 0; i < 2; i++ {
fmt.Println(s)
// runtime.Gosched() 的作用是让当前goroutine让出CPU,好让其它的goroutine获得执行的机会。
// 同时,当前的 goroutine 也会在未来的某个时间点继续运行。
runtime.Gosched()
}
}
func main() {
go test("1")
test("2")
}
// 输出结果
// 2
// 1
// 2
// 1
test("2")runtime.Gosched()go test("1")go test("1")runtime.Gosched()test(2)
goroutine 泄漏
Go语言带有内存自动回收的特性,因此内存一般不会泄漏。但是Goroutine 存在泄漏的情况,同时泄漏的 Goroutine引用的内存同样无法被回收,如下代码会造成泄漏:
上面的程序中后台Goroutine向通道输入自然数序列,main()函数中输出序列。但是当break跳出for循环的时候,后台Goroutine就处于无法被回收的状态了。我们可以通过context包来避免这个问题:
当main()函数在break跳出循环时,通过调用cancel()来通知后台Goroutine退出,这样就避免了Goroutine的泄漏。
强制退出进程,结束野生死循环协程
ctrl + c
package main
import (
"fmt"
"os"
"os/exec"
"os/signal"
"syscall"
)
func main() {
go exit()
}
// 监听退出
func exit() {
sigs := make(chan os.Signal, 1)
done := make(chan bool, 1)
signal.Notify(sigs, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
go func() {
sig := <-sigs
fmt.Println()
fmt.Println(sig)
done <- true
}()
fmt.Println("监听退出信号,PID: ", os.Getpid())
<-done
fmt.Println("退出")
// 查杀
exec.Command("killall", "main", fn.Tostring(os.Getpid())).Run()
// 自杀
exec.Command("kill", "-9", fn.Tostring(os.Getpid())).Run()
}