Анализ бесконечного цикла языка Go

задняя часть Go Mac Google

Я недавно читал статью,Как найти ошибку зависания процесса golang, который имеет этот фрагмент кода:

package main
import (
    "fmt"
    "io"
    "log"
    "net/http"
    "runtime"
    "time"
)
func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    go server()
    go printNum()
    var i = 1
    for {
        // will block here, and never go out
        i++
    }
    fmt.Println("for loop end")
    time.Sleep(time.Second * 3600)
}
func printNum() {
    i := 0
    for {
        fmt.Println(i)
        i++
    }
}
func HelloServer(w http.ResponseWriter, req *http.Request) {
    fmt.Println("hello world")
    io.WriteString(w, "hello, world!\n")
}
func server() {
    http.HandleFunc("/", HelloServer)
    err := http.ListenAndServe(":12345", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

Запустите, вы обнаружите, что он останавливается после печати числа на некоторое время, мы выполняем

curl localhost:12345

Программа зависла. О том, где зависает программаdlvимеет хорошие позиции:

dlv debug hang.go

После входа запускаем программу, печать останавливается и переходит в застрявшее состояние, выполняемctrl C,dlvПокажет, где разъединения:

received SIGINT, stopping process (will not forward signal)> main.main() ./hang.go:17 (PC: 0x12dd7c8)
    12: func main() {
    13:         runtime.GOMAXPROCS(runtime.NumCPU())
    14:         go server()
    15:         go printNum()
    16:         var i = 1
=>  17:         for {
    18:                 // will block here, and never go out
    19:                 i++
    20:         }
    21:         fmt.Println("for loop end")
    22:         time.Sleep(time.Second * 3600)
(dlv)

Но я все еще не понимаю, чего я не понимаю в основном потому, что:

  • Я прочитал еще две статьиКраткий анализ экземпляров планирования Goroutineа такжеТакже поговорим о планировщике goroutine, того же автора, Тони Бай, и очень хорошо написано. Во второй статье объясняется взаимосвязь между планированием горутин и количеством процессоров (не так много объяснений, предлагаю вам посмотреть на это), мой mac - двухъядерный четырехпоточный (студенты, которые не понимают здесь, сами гуглят ) cpu hyperthreading), версия go 1.9, теоретически может запускать 4 горутины без учета бесконечного цикла, бесконечный цикл убивает не более одного процессора, в приведенном выше коде всего 3 горутины, и все они как бы зависают.
  • Теоретически это не моя субъективная догадка, я убежал.1серединапервая статьяПример из:
package main
import (
    "fmt"
    "time"
)
func deadloop() {
    for {
    }
}
func main() {
    go deadloop()
    for {
        time.Sleep(time.Second * 1)
        fmt.Println("I got scheduled!")
    }
}

В приведенном выше коде есть две горутины, одна из которыхmain goroutine,одинdeadloop goroutine, время работыdeadloop gouroutineне правильноmain goroutineВоздействие, печать продолжается, и в статье автора объясняется, почему.

существуетКак найти ошибку зависания процесса golangЕсть такой отрывок:

Поскольку в цикле for нет вызова функции, компилятор не будет вставлять код планирования, поэтому горутина, выполняющая цикл for, не может быть вызвана, и если gc встретится во время цикла, он застрянет на этапе gcwaiting. , и весь процесс застрянет на стадии gcwaiting Навсегда зависнуть и умереть на этом цикле. и больше не отвечаю.

На самом деле это причина, по которой наш первый фрагмент кода застревает, а также причина, по которой наш второй фрагмент кода не застревает.gcначальство!

Давайте прочитаем другую статью,Механизм сборки мусора (GC) Golang, статья короткая, но на счету каждое предложение:

  1. Установите gcwaiting=1, это состояние будет проверяться один раз перед каждой задачей G, если да, то текущая M будет переведена в спящий режим;
  2. Если в этом М запущена долговременная G-задача, что делать?Будет ли она ждать, пока G-задача сама переключится? В этом случае надо ждать 10 мс, нельзя ждать! Определенно не могу дождаться!
    Поэтому он будет активно выдавать метку прерывания (аналогично предыдущей), чтобы текущая задача G была прервана, а при запуске следующей задачи G она переходила к шагу 1.

Таким образом, если в это время выполняется бесконечный цикл без вызовов функций, gc также выдает флаг вытеснения, но если в бесконечном цикле нет вызовов функций, то нет места для маркировки и не может быть вытеснен, поэтому он может быть только задаватьgcwaiting=1,а такжеМ не спит,stop the worldзастрял (тупик),gcwaitingВсегда 1 и вся программа зависает!

На самом деле, феномен первого кода был объяснен здесь.Я думаю, каждый может догадаться, почему второй код не завис: в коде нет срабатывания gc! Запустим его вручную:

package main
import (
    "fmt"
    "log"
    "net/http"
    _ "net/http/pprof"
    // "runtime"
    "time"
)
func deadloop() {
    for {
    }
}
func main() {
    go func() {
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()
    go deadloop()
    i := 3
    for {
        time.Sleep(time.Second * 1)
        i--
        fmt.Println("I got scheduled!")
        if i == 0 {
            runtime.GC()
        }
    }
}

Вы обнаружите, что после печати 3 строк программа тоже зависла, бинго🎉

Давайте посмотримgcwaitingРавно ли оно 1:

$ go build hang2.go
$ GODEBUG="schedtrace=300,scheddetail=1" ./hang2
SCHED 2443ms: gomaxprocs=4 idleprocs=3 threads=7 spinningthreads=0 idlethreads=2 runqueue=0 gcwaiting=0 nmidlelocked=0 stopwait=0 sysmonwait=0
  P0: status=1 schedtick=4 syscalltick=5 m=5 runqsize=0 gfreecnt=1
  P1: status=0 schedtick=14 syscalltick=0 m=-1 runqsize=0 gfreecnt=0
  P2: status=0 schedtick=3 syscalltick=4 m=-1 runqsize=0 gfreecnt=0
......  
SCHED 2751ms: gomaxprocs=4 idleprocs=0 threads=7 spinningthreads=0 idlethreads=2 runqueue=0 gcwaiting=1 nmidlelocked=0 stopwait=1 sysmonwait=0
  P0: status=1 schedtick=4 syscalltick=5 m=5 runqsize=0 gfreecnt=1
  P1: status=3 schedtick=14 syscalltick=0 m=-1 runqsize=0 gfreecnt=0
  P2: status=3 schedtick=3 syscalltick=10 m=-1 runqsize=0 gfreecnt=0
  P3: status=3 schedtick=1 syscalltick=26 m=0 runqsize=0 gfreecnt=0
  M6: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 helpgc=0 spinning=false blocked=false lockedg=-1
  M5: p=0 curg=19 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 helpg

Код меня не обманывает!

использованная литература