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

Go

Кто-то из группы golang спросил, почему программа необъяснимо зависает, а потом уже не отвечает ни на какие запросы. Одноядерный процессор заполнен.

Эта функция очень похожа на ситуацию, с которой столкнулась некая система нашей компании.После длительного анализа позиционирования и подведения итогов я также по-разному читал код времени выполнения и gc golang и, наконец, нашел тип, который появился в бизнес. Код выглядит следующим образом:

package main

import "time"

func main() {
    var ch = make(chan int, 100)
    go func() {
        for i := 0; i < 100; i++ {
            ch <- 1
        }
    }()

    for {
        // the wrong part
        if len(ch) == 100 {
            sum := 0
            itemNum := len(ch)
            for i := 0; i < itemNum; i++ {
                sum += <-ch
            }
            if sum == itemNum {
                return
            }
        }
    }

}

Зациклить основную горутину, чтобы определить, заполнены ли данные в ch, и поместить 100 фрагментов данных в ch другой горутины. Вроде бы программа должна завершиться нормально, да?

Если интересно, можете попробовать сами. Фактически, эта программа застрянет в следующем цикле for в немного более старых версиях golang. Причина?

использовать:

GODEBUG="schedtrace=300,scheddetail=1" ./test1

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

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

Конечно, вышеприведенная программа не будет зависать в последних версиях golang 1.8/1.9 (экспериментальные результаты, без подробностей). В примечании к обновлению официальный представитель заявил, что теоретически другие горутины будут иметь возможность планироваться в плотном цикле, поэтому мы решили поверить официальному лицу и попробовать следующую программу:

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) {
    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

Мне все еще не хочется доверять официальному лицу. После исследований и исследований я случайно написал, как найти такую ​​​​ошибку. Сначала проанализируйте характеристики программы, когда возникает этот тип ошибки:

1. 卡死在 for 循环上
2. gcwaiting=1
3. 没有系统调用

Поскольку системного вызова нет, это не вызвано системным вызовом pot, поэтому мы не можем использовать такие инструменты, как strace, чтобы увидеть, зависает ли программа на системном вызове. И gcwaiting=1 на самом деле не помогает нам определить, в чем проблема.

Затем остальные застревают в цикле for Интенсивные циклы for обычно приводят к заполнению ядра процессора. Если вы раньше занимались системным программированием, вы должны хорошо разбираться в инструменте perf, вы можете использовать:

perf top

Выборка использования ЦП, чтобы мы могли найти программные функции, которые используют верхний ЦП. Фактическиperf topРезультат выполнения также очень интуитивно понятен:

  99.52%  ffff                     [.] main.main
   0.06%  [kernel]                 [k] __do_softirq
   0.05%  [kernel]                 [k] 0x00007fff81843a35
   0.03%  [kernel]                 [k] mpt_put_msg_frame
   0.03%  [kernel]                 [k] finish_task_switch
   0.03%  [kernel]                 [k] tick_nohz_idle_enter
   0.02%  perf                     [.] 0x00000000000824d7
   0.02%  [kernel]                 [k] e1000_xmit_frame
   0.02%  [kernel]                 [k] VbglGRPerform

Видите ли, наша программа фактически застряла на функции main.main. Позиционирование за секунды с помощью одной команды.

Маме больше не нужно беспокоиться о том, что моя программа случайно напишет бесконечный цикл. На самом деле, иногда, почему мой обычный цикл становится бесконечным, не так просто проверить, как в простой демонстрации выше. В настоящее время вы также можете использовать delve, и недавно помог jsoniter найти ошибку, аналогичную приведенной выше:

GitHub.com/gin-a-oh-nickname/a…

Найдите функцию из perf, затем используйте pid для подключения к процессу, найдите горутину, которая выполняет цикл, а затем объедините вывод локальных переменных вплоть до следующего.

Проблема находится над.

Последняя строка мелким шрифтом: Спасибо г-ну Мао за его руководство в группе голанг.

Если вы считаете, что с написанием что-то не так, или вы отдаете дань уважения г-ну Мао, упомянутому в конце статьи, или вас очень интересует jsoniter, библиотека для разбора json, которая вот-вот появится на k8s, вы хотите знать больше об авторе мысленного путешествия jsoniter Даниэля, то у вас есть следующие варианты:

1. Присоединяйтесь к станции B и Мао Цзуну, чтобы изучить продвинутый уровень осанки.

2. Присоединяйтесь к Didi и автору jsoniter Даниэлю @taowen в качестве коллег.

3. Добавьте капли в спрей автора этой статьи

Резюме можно отправлять по адресу:caochunhui@didichuxing.com