DEV Community

loading...

Go program start from main function and stop when main function exit

#go
Weerasak Chongnguluam
Software Developer/Love to code/Teaching to code
・1 min read

โปรแกรมที่เราเขียน ไม่ว่าจะเขียนด้วยภาษาอะไร มันจะมีวงจรชีวิต (life cycle) ของมันว่าจะเริ่มทำงานที่จุดไหน และ สิ้นสุดที่ตรงไหน เสมอ

ถ้าเราเข้าใจ life cycle ของโปรแกรม ของภาษาที่เราใช้ หรือ เข้าใจ life cycle ของ runtime ที่เราใช้ เราก็จะไล่การทำงานของโปรแกรมได้ง่ายขึ้นเยอะเลย

อย่างโปรแกรมที่เขียนด้วย Go นั้นจะเริ่มทำงานที่ฟังก์ชัน main ใน package main และจะหยุดทำงานเมื่อ main ฟังก์ชันจบการทำงาน ไม่ว่าจะด้วยการ return ปกติ, การเรียก os.Exit ฟังก์ชัน หรือโดน signal จาก OS ให้หยุดทำงาน

ตัวอย่างเช่นโค้ดแบบนี้

package main

import (
    "fmt"
)

func main() {
    fmt.Println("Hello, playground")
}
Enter fullscreen mode Exit fullscreen mode

เมื่อเรารันก็ได้ข้อความ Hello, playground ในออกไปที่ standard output แต่ถ้าเราเปลี่ยนตรง fmt.Println ให้ไปไปรันในอีก goroutine ล่ะ แบบนี้

package main

import (
    "fmt"
)

func main() {
    go func() {
        fmt.Println("Hello, playground")
    }()
}
Enter fullscreen mode Exit fullscreen mode

เวลารันกลับไปเห็นเกิดอะไรขึ้นเลย นั่นเพราะว่า พอ main ฟังก์ชันเริ่มทำงาน มันสร้าง goroutine ใหม่ เสร็จแล้วมันก็จบการทำงานของตัวเองโดยไม่รอให้ฟังก์ชันของ goroutine อีกอันที่สร้างใหม่ได้ทำงานเลย เมื่อ main จบการทำงาน ถือว่าจบโปรแกรม goroutine ที่เหลือก็จบการทำงานด้วยไปโดยอัตโนมัติ

ถ้าอยากให้ main รอให้ goroutine ที่แยกไปทำงานเสร็จก่อนก็ทำได้แบบนี้

package main

import (
    "fmt"
)

func main() {
    done := make(chan struct{})
    go func() {
        fmt.Println("Hello, playground")
        close(done)
    }()
    <-done
}
Enter fullscreen mode Exit fullscreen mode

ใช้ channel ช่วย โดยให้ main รอรับค่า ส่วนใน goroutine เมื่อทำงานเสร็จก็ close channel ซะทำให้ตรงส่วนที่รอรับค่าไม่ block เลยทำให้ main จบการทำงานต่อไปได้

ทีนี้ตัวอย่างถัดไป โค้ด HTTP server ง่ายๆแบบนี้

package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "Hello, World")
    })

    http.ListenAndServe(":8000", nil)
}
Enter fullscreen mode Exit fullscreen mode

จะเห็นว่า โปรแกรมเรามันรันแล้วค้างไว้ รอรับ request มาที่ port 8000 แล้วตอบกลับไปด้วยข้อความ Hello, World โดยที่จบการทำงานเมื่อเราสั่ง ctrl+c เพื่อให้ OS ส่ง signal ไปจบ process

แสดงว่าโค้ดที่เราเรียก http.ListenAndServe มันเป็นฟังก์ชันที่รันนานๆเลยไม่จบจนกว่าจะมี signal มาหยุดมันนั่นเอง มันเลย block main ฟังก์ชันเอาไว้ให้ยังรันอยู่ไม่ไปไหน

ถ้าเราไล่โค้ดใน http.ListenAndServe เราจะไต่ไปถึงจุดที่เรียก method (srv *Server) Serve(l net.Listener) error ที่มีโค้ด for loop แบบนี้อยู่

for {
        rw, err := l.Accept()
        if err != nil {
            select {
            case <-srv.getDoneChan():
                return ErrServerClosed
            default:
            }
            if ne, ok := err.(net.Error); ok && ne.Temporary() {
                if tempDelay == 0 {
                    tempDelay = 5 * time.Millisecond
                } else {
                    tempDelay *= 2
                }
                if max := 1 * time.Second; tempDelay > max {
                    tempDelay = max
                }
                srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
                time.Sleep(tempDelay)
                continue
            }
            return err
        }
        connCtx := ctx
        if cc := srv.ConnContext; cc != nil {
            connCtx = cc(connCtx, rw)
            if connCtx == nil {
                panic("ConnContext returned nil")
            }
        }
        tempDelay = 0
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew, runHooks) // before Serve can return
        go c.serve(connCtx)
    }
Enter fullscreen mode Exit fullscreen mode

มันคือ infinity loop ที่ทำงานรอรับ request แล้วส่งไปโปรเซสต่อเพื่อทำงานตาม handler ที่เรา register เอาไว้ใน main ฟังก์ชันนั่นเอง

การที่เรารู้ว่าทุกอย่างเริ่มที่ main ฟังก์ชัน แต่ก็ไม่จำเป็นว่าเราต้องไล่โค้ดเริ่มที่ main ฟังก์ชันเสมอไป เพราะโค้ดใน main ฟังก์ชันจะเป็นการ initialize ค่าเริ่มต้นของสิ่งต่างๆมากกว่าเป็น business logic จริงๆแล้วเราเป็นคนออกแบบ life cycle ของโปรแกรมเราอีกทีว่า จะทำงานอะไร เมื่อมีเงื่อนไขแบบไหน เช่นถ้าเราทำ Web API เราสนใจแค่การทำงานของ endpoint หนึ่ง เราก็ไม่จำเป็นต้องเริ่มที่ main ฟังก์ชันก็ได้ แต่ขอให้เรารู้ว่าสิ่งต่างๆถูก setup เริ่มต้นมาจาก main ก่อน ก่อนที่จะเริ่ม life cycle ในการรับ request แล้ว process response นั่นเอง

Discussion (1)

Collapse
arvindpdmn profile image
Arvind Padmanabhan

Good beginner article. Someone looking for an intro to Go without the code, see devopedia.org/go-language