ข้ามไปยังเนื้อหา

Goroutines Preview

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 พร้อมกันได้เป็นล้านตัว

keyword go เปิด function ใน goroutine ใหม่ — execution context ที่เบาและถูก schedule แยกอิสระ คำสั่งนี้ return ทันที แล้ว goroutine ก็รัน concurrent ต่อไป

TypeScript
// 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
// 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 จำนวนที่รู้แน่นอนให้เสร็จก่อน main goroutine จะออก ให้นึกถึงเป็น counter ที่ block จนกว่าจะนับถึงศูนย์

TypeScript
// TypeScript — Promise.all รอทั้งหมด
const results = await Promise.all(
[1, 2, 3].map(id => fetchUser(id))
);
console.log(results);
Go
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 คือท่อส่งข้อมูลที่มี type สำหรับส่งค่าระหว่าง goroutine คุณ send ค่าด้วย <- และ receive ค่าด้วย <- Channel จะ synchronize ระหว่าง sender กับ receiver คือการ send จะ block จนกว่าจะมีคนรับ และการ receive ก็ block จนกว่าจะมีคนส่ง (สำหรับ unbuffered channel)

TypeScript
// TypeScript — ไม่มี channel primitive ใน native
// ใกล้ที่สุดคือ queue + event emitter หรือ stream
import { Readable } from "stream";
// ... ต้องการ plumbing ซับซ้อน
Go
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")
}
จะเปิด goroutine ใน Go ได้อย่างไร?
goroutine ใหม่เริ่มต้นด้วย stack memory ประมาณเท่าไหร่?
sync.WaitGroup.Wait() ทำอะไร?
การ send ไปยัง unbuffered channel block จนกว่า: