协程独占 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()
}