GoLang: โค้ด Concurrency แบบง่ายๆ ด้วย Goroutine และ Channel

สวัสดีครับทุกคน! วันนี้เรามาคุยเรื่อง GoLang กันบ้างดีกว่า คือหลายคนอาจจะยังไม่เคยลอง หรือเคยลองแล้วแต่ยังงงๆ ว่าไอ้ Goroutine กับ Channel ที่เค้าพูดถึงกันนักหนาเนี่ย มันดียังไง?

ยอมรับเลยว่าตอนแรกผมก็ไม่ได้อินอะไรกับ Go มากนะ แต่พอได้มาลองเขียนโค้ดที่มันต้องทำงานพร้อมๆ กัน (Concurrency) เยอะๆ อ่ะ โห... Go นี่ตอบโจทย์มากจริงๆ เพราะมันออกแบบมาให้ทำเรื่องพวกนี้ง่ายตั้งแต่แรกเลย ไม่ต้องมานั่งจัดการ Thread วุ่นวายเหมือนภาษาอื่น

Goroutine มันคืออะไร?

คิดง่ายๆ ว่า Goroutine มันก็คือ function ที่รันพร้อมๆ กันไปกับ main function ของเรานั่นแหละครับ แต่มันเบากว่า Thread ทั่วไปเยอะมากๆ เบาจนแบบ เราสร้างเป็นแสนๆ ตัวมันก็ยังไหวอะ! ไม่ต้องกลัวเครื่องจะค้าง

วิธีสร้าง Goroutine ก็โคตรง่าย แค่เติม go เข้าไปหน้า function call แค่นั้นเอง

package main

import (
    "fmt"
    "time"
)

func sayHello() {
    time.Sleep(2 * time.Second) // แกล้งทำเป็นว่าทำงานนานๆ หน่อย
    fmt.Println("สวัสดีครับจาก Goroutine!")
}

func main() {
    go sayHello() // เรียก function sayHello ให้รันเป็น Goroutine
    fmt.Println("โปรแกรมหลักรันต่อเลยนะ ไม่ได้รอ")
    time.Sleep(3 * time.Second) // รอให้ Goroutine ทำงานเสร็จก่อน ไม่งั้นโปรแกรมหลักจบไปก่อน
    fmt.Println("โปรแกรมหลักจบแล้ว.")
}

ถ้าลองรันโค้ดข้างบน จะเห็นว่า โปรแกรมหลักรันต่อเลยนะ ไม่ได้รอ จะโผล่มาก่อน สวัสดีครับจาก Goroutine! นั่นแหละครับคือการทำงานแบบ Concurrency!

ปัญหานิดหน่อย: สังเกตว่าผมต้องใส่ time.Sleep ไว้ใน main ด้วย ไม่งั้นโปรแกรมมันจะจบไปก่อนที่ sayHello จะทำงานเสร็จอะ เพราะ Goroutine มันไม่ได้บล็อค main ไง ทีนี้จะสื่อสารกันยังไงล่ะ? ก็ต้องใช้ Channel นี่แหละ

Channel คืออะไร?

Channel ก็เหมือนท่อ หรือช่องทางสื่อสารระหว่าง Goroutine นั่นแหละครับ มันช่วยให้ Goroutine ส่งข้อมูลถึงกันได้แบบปลอดภัย และยังเป็นวิธีที่ดีในการซิงค์การทำงานด้วย

package main

import (
    "fmt"
    "time"
)

func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        fmt.Printf("Worker %d กำลังประมวลผลงาน %d\n", id, j)
        time.Sleep(time.Second) // แกล้งทำว่าใช้เวลาประมวลผล
        results <- j * 2       // ส่งผลลัพธ์กลับไป
    }
}

func main() {
    const numJobs = 5
    jobs := make(chan int, numJobs)    // สร้าง channel สำหรับงานที่จะส่ง
    results := make(chan int, numJobs) // สร้าง channel สำหรับผลลัพธ์

    // สร้าง Goroutine worker 3 ตัว
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }

    // ส่งงานไปให้ worker
    for j := 1; j <= numJobs; j++ {
        jobs <- j
    }
    close(jobs) // บอกว่าไม่มีงานเพิ่มแล้วนะ ปิด channel งาน

    // รอรับผลลัพธ์จาก worker
    for a := 1; a <= numJobs; a++ {
        <-results
    }
    // ไม่จำเป็นต้อง close(results) เพราะเดี๋ยวโปรแกรมก็จบละ
    fmt.Println("ทุกงานเสร็จสมบูรณ์!")
}

ในตัวอย่างนี้: * เราสร้าง jobs channel เพื่อส่งงาน (เลขจำนวนเต็ม) และ results channel เพื่อรับผลลัพธ์ * มี worker Goroutine 3 ตัว คอยรับงานจาก jobs และส่งผลลัพธ์ไปที่ results * <-chan int คือ channel ที่รับได้แค่ (receive-only) * chan<- int คือ channel ที่ส่งได้อย่างเดียว (send-only) * close(jobs) สำคัญมาก! ถ้าไม่ปิด jobs channel ตัว for j := range jobs ใน worker มันจะค้างรอไปเรื่อยๆ ครับ เพราะมันไม่รู้ว่างานหมดแล้วรึยัง

ข้อควรระวัง (ที่ผมเคยพลาดบ่อยๆ): * Deadlock: ถ้า channel ไม่มีใครส่ง ไม่มีใครรับ หรือรับไม่ครบ จบเลยครับ! โปรแกรมจะค้าง หรือ panic ว่า fatal error: all goroutines are asleep - deadlock! บ่อยมากที่ลืม close channel หรือลืมรับค่าจนครบอะ * Buffer Size: make(chan int, 5) คือการสร้าง channel ที่มี buffer 5 ช่อง ถ้าไม่ใส่ buffer หรือใส่ make(chan int) เฉยๆ มันจะเป็น unbuffered channel ที่ต้องมีคนรอรับทันทีที่ส่ง ไม่งั้นก็บล็อคเหมือนกัน อันนี้แล้วแต่ use case นะ แต่ถ้าใช้แล้วงงๆ ลองคิดถึง buffer ดูครับ

ส่วนตัวผมมองว่า Go นี่แหละคืออนาคตของงานที่ต้องการ Concurrency สูงๆ เพราะมันใช้ง่าย เรียนรู้ไม่ยาก แถม performance ก็ดีมากๆ ด้วย ถ้ามีโปรเจคที่ต้องทำอะไรพร้อมๆ กันเยอะๆ หรือสร้าง API ที่ต้องการความเร็ว Go เป็นตัวเลือกที่ไม่ควรมองข้ามเลยจริงๆ ครับ ลองเอาไปเล่นดูนะ!

Read more

PSG vs Monaco: ศึก 100 นัดเดือด ลีกเอิง และบทเรียนที่ปาร์ค เดส์ แพร็งซ์

PSG vs Monaco: ศึก 100 นัดเดือด ลีกเอิง และบทเรียนที่ปาร์ค เดส์ แพร็งซ์

สรุปผลและวิเคราะห์เกมเดือด PSG พบ Monaco ในลีกเอิงนัดที่ 25 ซึ่งเป็นการพบกันครั้งที่ 100 ในประวัติศาสตร์ลีก ความพ่ายแพ้ 1-3 คาบ้านของ PSG และบทบาทของ Akliouche พร้อมผลกระทบต่อเส้นทางแชมเปี้ยนส์ลีก

By ทีมงาน devdog
บาเยิร์นผงาดไร้เคน! ถล่มกลัดบัค 4-1 โชว์ความลึกของทีมก่อนลุยศึก UCL

บาเยิร์นผงาดไร้เคน! ถล่มกลัดบัค 4-1 โชว์ความลึกของทีมก่อนลุยศึก UCL

บาเยิร์น มิวนิค โชว์ฟอร์มแกร่ง แม้ไม่มีแฮร์รี่ เคน ถล่ม โบรุสเซีย มึนเชนกลัดบัค 4-1 ก่อนเตรียมลุยศึกแชมเปียนส์ลีกกับอตาลันต้า!

By ทีมงาน devdog
PSG: มหาอำนาจลูกหนังฝรั่งเศส กับศึกดวลเดือดโมนาโก และเป้าหมายสู่บัลลังก์ยุโรป

PSG: มหาอำนาจลูกหนังฝรั่งเศส กับศึกดวลเดือดโมนาโก และเป้าหมายสู่บัลลังก์ยุโรป

เจาะลึกเส้นทาง PSG สู่มหาอำนาจลูกหนัง วิเคราะห์สถานการณ์ลีกเอิง เตรียมพร้อมศึกใหญ่กับโมนาโก พร้อมความมุ่งมั่นสู่แชมป์ยุโรป

By ทีมงาน devdog
ลาลีกา: มนต์เสน่ห์ฟุตบอลสเปน, นวัตกรรมเรโทร, และบิ๊กแมตช์แห่งอนาคต

ลาลีกา: มนต์เสน่ห์ฟุตบอลสเปน, นวัตกรรมเรโทร, และบิ๊กแมตช์แห่งอนาคต

สำรวจลาลีกา ฟุตบอลสเปนอันทรงเสน่ห์ พร้อมไฮไลต์บิ๊กแมตช์ 2025/26 นวัตกรรมสัปดาห์เรโทร และบทบาทต่อวัฒนธรรมและเศรษฐกิจ.

By ทีมงาน devdog