Goroutines Preview
JavaScript: concurrency model ด้วย async/await
หัวข้อที่มีชื่อว่า “JavaScript: concurrency model ด้วย async/await”JavaScript เป็น single-threaded ความ concurrent มาจาก event loop พอ async operation (network, I/O) ทำงานเสร็จ event loop ก็เอา callback เข้าคิวรอ ส่วน async/await เป็นแค่ syntactic sugar บน Promise ที่ทำให้โค้ด async อ่านเหมือน synchronous แต่สุดท้ายก็ยังมีแค่ thread เดียวที่รันโค้ด JavaScript ของเราได้ในแต่ละช่วงเวลา
Go ใช้แนวทางที่ต่างออกไปสิ้นเชิง: goroutine คือ lightweight thread ที่ concurrent จริง ๆ โดย Go runtime จะ schedule กระจายลงบน OS thread หลายตัว เครื่องทั่วไปรัน goroutine พร้อมกันได้เป็นล้านตัว
การเปิด goroutine
หัวข้อที่มีชื่อว่า “การเปิด goroutine”keyword go เปิด function ใน goroutine ใหม่ — execution context ที่เบาและถูก schedule แยกอิสระ คำสั่งนี้ return ทันที แล้ว goroutine ก็รัน concurrent ต่อไป
// TypeScript — async/await (event loop, single thread)async function fetchUser(id: number): Promise<User> { const res = await fetch(`/users/${id}`); return res.json();}
async function main() { // รัน sequential โดยค่าเริ่มต้น: const u1 = await fetchUser(1); const u2 = await fetchUser(2);
// เพื่อรัน concurrent ใช้ Promise.all: const [u1, u2] = await Promise.all([fetchUser(1), fetchUser(2)]);}// Go — goroutine (OS-thread-backed, parallel จริงๆ)func fetchUser(id int) User { // ... actual network call return User{ID: id}}
func main() { // keyword go — เปิด fetchUser แบบ concurrent go fetchUser(1) // fire and forget (ผลลัพธ์หาย) go fetchUser(2)
// เพื่อรับผลลัพธ์ ใช้ channel หรือ sync.WaitGroup}sync.WaitGroup — รอ goroutine เสร็จ
หัวข้อที่มีชื่อว่า “sync.WaitGroup — รอ goroutine เสร็จ”sync.WaitGroup คือวิธีที่ง่ายที่สุดในการรอ goroutine จำนวนที่รู้แน่นอนให้เสร็จก่อน main goroutine จะออก ให้นึกถึงเป็น counter ที่ block จนกว่าจะนับถึงศูนย์
// TypeScript — Promise.all รอทั้งหมดconst results = await Promise.all( [1, 2, 3].map(id => fetchUser(id)));console.log(results);func processItem(id int, wg *sync.WaitGroup) { defer wg.Done() // ลด counter เมื่อเสร็จ fmt.Printf("processing %d\n", id)}
func main() { var wg sync.WaitGroup
for i := 1; i <= 3; i++ { wg.Add(1) // เพิ่ม counter go processItem(i, &wg) // เปิด goroutine }
wg.Wait() // block จนกว่า counter == 0 fmt.Println("all done")}Channel — สื่อสารระหว่าง goroutine
หัวข้อที่มีชื่อว่า “Channel — สื่อสารระหว่าง goroutine”Channel คือท่อส่งข้อมูลที่มี type สำหรับส่งค่าระหว่าง goroutine คุณ send ค่าด้วย <- และ receive ค่าด้วย <- Channel จะ synchronize ระหว่าง sender กับ receiver คือการ send จะ block จนกว่าจะมีคนรับ และการ receive ก็ block จนกว่าจะมีคนส่ง (สำหรับ unbuffered channel)
// TypeScript — ไม่มี channel primitive ใน native// ใกล้ที่สุดคือ queue + event emitter หรือ streamimport { Readable } from "stream";// ... ต้องการ plumbing ซับซ้อนfunc producer(ch chan<- int) { for i := 0; i < 5; i++ { ch <- i // ส่ง i เข้า channel } close(ch) // signal ว่าไม่มีค่าอีกแล้ว}
func main() { ch := make(chan int) // unbuffered channel
go producer(ch) // รัน concurrent
for v := range ch { // รับจนกว่า channel ถูก close fmt.Println("received:", v) }}package main
import ( "fmt" "sync")
func fetchData(id int, wg *sync.WaitGroup, results chan<- string) { defer wg.Done() // จำลองงาน result := fmt.Sprintf("result from worker %d", id) results <- result}
func main() { const numWorkers = 3 var wg sync.WaitGroup results := make(chan string, numWorkers) // buffered channel
for i := 1; i <= numWorkers; i++ { wg.Add(1) go fetchData(i, &wg, results) }
// ปิด channel เมื่อ goroutine ทั้งหมดเสร็จ go func() { wg.Wait() close(results) }()
// รวบรวมผลลัพธ์ for r := range results { fmt.Println(r) } fmt.Println("all workers done")}Loading Go runtime (first run only, ~8 MB)…