近期遇到的3個Golang代碼問題

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

這兩周來業餘時間都在用Golang寫代碼,現在處於這樣一個狀態:除了指令碼,就是Golang了。反正能用golang實現的,都用golang寫。

Golang語言相對成熟了,但真正寫起來,還是要注意一些“坑”的,下面是這周遇到的三個問題,這裡分享出來,希望能對遇到同樣問題的童鞋有所協助。

一、誤用定時器,狂佔CPU

golang中有一個通過channel實現timeout或tick timer的非常idiomatic的方法,代碼如下:

func worker(start chan bool) {
        for {
                timeout := time.After(30 * time.Second)
                 select {
                         // … do some stuff
                         case <- timeout:
                                 return
                 }
        }
}

func worker(start chan bool) {
        for {
                heartbeat := time.Tick(30 * time.Second)
                 select {
                         // … do some stuff
                         case <- heartbeat:
                                 return
                 }
        }
}

沒錯,就像上面這兩個例子,如果你單獨執行它們,你不會發現任何問題,但是當你將這樣的代碼放到一個7 * 24小時的Service中,並且timeout間隔或heartbeat間隔為更短時間,比如1s時,問題就出現了。

我的程式最初就是用上面的代碼實現了一個timewheel,通過放置在一個單獨goroutine中的定時器檢測timewheel是否有到期的 timer。程式跑在後台啟動並執行很好,直到有一天晚上我無意中執行了一下top,我發現這個service居然站用了40%多的CPU負荷。最初我懷疑是 不是代碼中有死迴圈,但仔細巡查一遍代碼後,沒有發現死迴圈的痕迹,演算法邏輯也沒問題。

於是重啟了一下這個service,發現cpu佔用降了下來。出去去了趟衛生間,回來繼續用top觀察,不好,這個service佔用了1%的CPU,再 過一會升到2%,觀察一段時間後,發現這個service對cpu的佔用率隨著時間的推移而增加。gdb attach了相應的進程號,stack多是go runtime的調度。

再次回到代碼,發現可能存在問題的只有這裡的tick。我的tick間隔是1s。這樣每1s都會建立一個runtime timer,而通過runtime的源碼來看,這些timer都扔給了runtime調度(一個heap)。時間長了,就會有超多的timer需要 runtime調度,不耗CPU才怪。

於是做了如下修改:

func worker(start chan bool) {
        heartbeat := time.Tick(1 * time.Second)
         for {
               
                 select {
                         // … do some stuff
                         case <- heartbeat:
                                 return
                 }
        }
}

重新編譯執行service,觀察了一天,cpu再也沒有升高過。

二、小心list.List的Delete邏輯

其實這是一個在哪種語言中都會遇到的初級問題,這裡只是給大家提個醒罷了。不多說了,上代碼:

從一個list.List中刪除一個element,一般邏輯是:

l := list.New()
… …
for e := l.Front(); e != nil; e = e.Next() {
        if e.Value.(int) == someValue {
                l.Remove(e)
                return or break
        }
}

但是如果list裡有重複元素,且代碼要遍曆整個list刪除某個值為somevalue的元素呢?上面的一般方法是由邏輯缺陷的,例子:

func foo(i int) {
        l := list.New()
        for i := 0; i < 9; i++ {
                l.PushBack(i)
        }
        l.PushBack(6)

        for e := l.Front(); e != nil; e = e.Next() {
                fmt.Print(e.Value.(int))
        }

        for e := l.Front(); e != nil; e = e.Next() {
                if e.Value.(int) == i {
                        l.Remove(e)
                }
        }

        fmt.Printf("\n")
        for e := l.Front(); e != nil; e = e.Next() {
                fmt.Print(e.Value.(int))
        }
        fmt.Printf("\n")
}

func main() {
        foo(6)
}

該程式試圖刪除list中的所有值為6的element,但執行結果卻是:

go run testlist.go
0123456786
012345786

list中尾部的那個6沒有被刪除,程式似乎在刪除完第一個6之後就不再繼續迴圈了。事實也是這樣:

當l.Remove(e)執行後,e.Next()被置為了nil,這樣迴圈條件不再滿足,迴圈終止。

為此,對於這樣的程式,下面的方法才是正確的:

func main() {
        bar(6)
}

func bar(i int) {
        l := list.New()
        for i := 0; i < 9; i++ {
                l.PushBack(i)
        }
        l.PushBack(6)

        for e := l.Front(); e != nil; e = e.Next() {
                fmt.Print(e.Value.(int))
        }

        var next *list.Element
        for e := l.Front(); e != nil; {
                if e.Value.(int) == i {
                        next = e.Next()
                        l.Remove(e)
                        e = next
                } else {
                        e = e.Next()
                }
        }

        fmt.Printf("\n")
        for e := l.Front(); e != nil; e = e.Next() {
                fmt.Print(e.Value.(int))
        }
        fmt.Printf("\n")
}

執行結果:
$ go run testlist.go
0123456786
01234578

三、要給template起個正確的名字

編寫一個Web程式,需要用到html/template。

… …
t := template.New("My Reporter")
t, err = t.ParseFiles("views/report.html")
if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        return
}

t.Execute(w, UserInfo{xx: XX})

結果一執行卻crash了:

[martini] PANIC: runtime error: invalid memory address or nil pointer dereference
/usr/local/go/src/runtime/panic.go:387 (0×16418)
/usr/local/go/src/runtime/panic.go:42 (0x1573e)
/usr/local/go/src/runtime/sigpanic_unix.go:26 (0x1bb50)
/usr/local/go/src/html/template/template.go:59 (0x7ed64)
/usr/local/go/src/html/template/template.go:75 (0x7ef0d)
/Users/tony/Test/GoToolsProjects/src/git.oschina.net/bigwhite/web/app.go:104 (0x2db0)
    reportHandler: t.Execute(w, UserInfo{xx: XXX})

問題在t.Execute這行,單獨把template代碼摘出來放在一個測試代碼中:

//testtmpl.go
type UserInfo struct {
        Name string
}

func main() {
        t := template.New("My Reporter")
        t, err := t.ParseFiles("views/report.html")
        if err != nil {
                fmt.Println("parse error")
                return
        }

        err = t.Execute(os.Stdout, UserInfo{Name: "tonybai"})
        if err != nil {
                fmt.Println("exec error", err)
        }
        return
}

執行結果:
go run testtmpl.go
exec error template: My Reporter: "My Reporter" is an incomplete or empty template; defined templates are: "report.html"

看起來似乎template對象與模板名字對不上導致的錯誤啊。修改一下:

t := template.New("report.html")

執行結果:





    Hello, tonybai

這回對了,看來template的名字在與ParseFiles一起使用時不是隨意取的,務必要與模板檔案名稱字相同。

ParseFiles支援解析多個檔案,如果是傳入多個檔案該咋辦?godoc說了,template名字與第一個檔案名稱相同即可。

2015, bigwhite. 著作權.

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.