Я недавно читал статью,Как найти ошибку зависания процесса 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В этой статье упоминается
gcwaiting
, однако без объяснения причин.
существуетКак найти ошибку зависания процесса golangЕсть такой отрывок:
Поскольку в цикле for нет вызова функции, компилятор не будет вставлять код планирования, поэтому горутина, выполняющая цикл for, не может быть вызвана, и если gc встретится во время цикла, он застрянет на этапе gcwaiting. , и весь процесс застрянет на стадии gcwaiting Навсегда зависнуть и умереть на этом цикле. и больше не отвечаю.
На самом деле это причина, по которой наш первый фрагмент кода застревает, а также причина, по которой наш второй фрагмент кода не застревает.gc
начальство!
Давайте прочитаем другую статью,Механизм сборки мусора (GC) Golang, статья короткая, но на счету каждое предложение:
- Установите gcwaiting=1, это состояние будет проверяться один раз перед каждой задачей G, если да, то текущая M будет переведена в спящий режим;
- Если в этом М запущена долговременная 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
Код меня не обманывает!