12/04/2026 18:16น.

JS2GO EP.41 การจัดการ Concurrency Patterns ขั้นสูงใน Go และ JavaScript
#Fan-in Fan-out
#Worker Pool
#JavaScript
#Go
#Concurrency
ในระบบขนาดใหญ่ Concurrency ไม่ได้แค่ “รันงานหลายอย่างพร้อมกัน” แต่คือ การออกแบบวิธีจัดลำดับ ควบคุม และส่งต่อข้อมูล ระหว่าง tasks ให้ เสถียร รวดเร็ว และประหยัดทรัพยากร ทั้ง JavaScript (Node.js) และ Go (Golang) รองรับ concurrency
แต่มีสถาปัตยกรรมต่างกันอย่างมาก:
| ภาษา | โมเดลหลัก |
|---|---|
| JavaScript | Event Loop + async/await (single-threaded) |
| Go | Goroutines + Channels (multi-threaded lightweight) |
ใน EP.41 นี้ เราจะเจาะลึก 4 Concurrency Patterns ที่ใช้กันจริงใน Production พร้อมตัวอย่างโค้ดทั้ง JavaScript และ Go แบบเปรียบเทียบกันชัด ๆ:
✅ Worker Pool
✅ Fan-in / Fan-out
✅ Rate Limiter
✅ Pipeline Optimization
1. Worker Pool Pattern 👷♀️ จำกัดจำนวนงานที่รันพร้อมกัน
ทำไมต้อง Worker Pool?
หากสร้าง goroutine หรือ worker thread จำนวนมากเกินไป ระบบอาจ:
- ใช้ CPU พุ่งสูงผิดปกติ
- กินหน่วยความจำมาก
- ทำให้ event loop / scheduler overload
Worker Pool ช่วย ควบคุมจำนวนงานที่รันพร้อมกันให้เหมาะสมกับทรัพยากรของเครื่อง
🔹 Worker Pool ใน Go (ออกแบบมาสำหรับงานนี้โดยเฉพาะ)
package main
import (
"fmt"
"sync"
)
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for j := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, j)
results <- j * 2
}
}
func main() {
const numWorkers = 3
jobs := make(chan int, 5)
results := make(chan int, 5)
var wg sync.WaitGroup
for w := 1; w <= numWorkers; w++ {
wg.Add(1)
go worker(w, jobs, results, &wg)
}
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
wg.Wait()
close(results)
for r := range results {
fmt.Println("✅ Result:", r)
}
}
✔ จุดเด่นใน Go
- goroutine ราคาถูก (memory ~2KB)
- channel ใช้ควบคุม flow โดยไม่ต้อง lock เยอะ
- worker ทำงาน parallel จริงในระดับ OS thread
🔹 Worker Pool ใน JavaScript (ต้องใช้ Worker Threads)
const { Worker } = require('worker_threads');
const jobs = [1, 2, 3, 4, 5];
const maxWorkers = 3;
let active = 0;
function startWorker(job) {
return new Promise((resolve) => {
const worker = new Worker(`
const { parentPort } = require('worker_threads');
parentPort.postMessage(${job} * 2);
`, { eval: true });
worker.on('message', resolve);
});
}
async function runPool() {
const results = [];
for (const job of jobs) {
while (active >= maxWorkers)
await new Promise((r) => setTimeout(r, 10));
active++;
startWorker(job).then((res) => {
results.push(res);
active--;
});
}
while (results.length < jobs.length)
await new Promise((r) => setTimeout(r, 10));
console.log("✅ Results:", results);
}
runPool();
⚠️ ข้อจำกัดใน JavaScript
- worker_threads มี overhead สูงกว่า goroutine
- ต้องจัดการ message passing ด้วยตนเอง
- parallel ได้จริง แต่ code complexity สูงกว่า
2. Fan-out / Fan-in Pattern 🌪 กระจายงานและรวมผลลัพธ์กลับมา
ใช้เมื่อ:
- ต้องแยกงานออกเป็นหลายส่วนประมวลผลพร้อมกัน
- แล้วรวมผลลัพธ์กลับมาที่จุดเดียว
🔹 Fan-in / Fan-out ใน Go (ธรรมชาติของ channel)
func producer(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
func worker(id int, in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
fmt.Printf("Worker %d processing %d\n", id, n)
out <- n * 2
}
close(out)
}()
return out
}
func fanIn(chs ...<-chan int) <-chan int {
out := make(chan int)
var wg sync.WaitGroup
for _, ch := range chs {
wg.Add(1)
go func(c <-chan int) {
defer wg.Done()
for n := range c {
out <- n
}
}(ch)
}
go func() {
wg.Wait()
close(out)
}()
return out
}
func main() {
in := producer(1, 2, 3, 4, 5)
out1 := worker(1, in)
out2 := worker(2, in)
out3 := worker(3, in)
results := fanIn(out1, out2, out3)
for r := range results {
fmt.Println("Result:", r)
}
}
✔ จุดแข็งของ Go
- channel ทำงานเป็น queue โดยธรรมชาติ
- fan-in/fan-out scale ได้ดีมาก
- ทำงาน parallel จริงด้วย goroutine
🔹 Fan-out / Fan-in ใน JavaScript
async function worker(id, data) {
await new Promise((r) => setTimeout(r, 100));
console.log(`Worker ${id} processed ${data}`);
return data * 2;
}
async function fanOut(data) {
const promises = data.map((d, i) => worker(i + 1, d));
return Promise.all(promises);
}
(async () => {
const results = await fanOut([1, 2, 3, 4, 5]);
console.log("Results:", results);
})();
ข้อสังเกต:
- Fast สำหรับ I/O-bound
- ไม่ parallel CPU จริง (เว้นแต่ใช้ worker threads)
3. Rate Limiter Pattern ⏱ จำกัดความถี่ของงานเพื่อป้องกัน overload
🔹 Go: ใช้ time.Ticker / Token Bucket ได้ง่ายมาก
limiter := time.Tick(500 * time.Millisecond)
for i := 1; i <= 5; i++ {
<-limiter
fmt.Println("Processing request", i)
}
🔹 JavaScript: ใช้ await + delay
async function rateLimited(tasks, interval = 500) {
for (const [i, task] of tasks.entries()) {
await new Promise((r) => setTimeout(r, interval));
console.log(`Processing task ${i + 1}`);
task();
}
}
4. Pipeline Optimization ⚙️
เทคนิคเพื่อเพิ่ม Performance
ใน Go
- ใช้ buffered channel
- ใช้ sync.Pool ลด allocation
- ลดการ copy data โดยใช้ pointer
ใน JavaScript
- ใช้ Stream แทนการโหลดข้อมูลทั้งหมดเข้าหน่วยความจำ
- ใช้ async generator
- ลดการสร้าง object ใน loop
สรุปเปรียบเทียบ
| Pattern | Go | JavaScript |
|---|---|---|
| Worker Pool | ⭐ ง่าย + ทรงพลัง | ต้องใช้ worker_threads |
| Fan-in/out | ⭐ channel เหมาะที่สุด | Promise-based จำกัด parallel |
| Rate Limiter | ใช้ ticker/token bucket | ใช้ delay/queue |
| Pipeline Optimization | รองรับ production-scale ง่าย | Stream ดี แต่ไม่ parallel CPU |
✨ ข้อสรุปสุดท้าย
- ถ้าระบบของคุณต้อง concurrency หนัก ๆ, CPU-bound, data pipeline → Go เหมาะที่สุด
- ถ้าระบบของคุณเป็น I/O-bound, API-first, real-time web → JavaScript เหมาะมาก
ตอนต่อไป
ใน EP.42 JS2GO เราจะเจาะลึก: ⚙️ “Goroutine Pools และ Worker Pools ใน Go และ JavaScript” คุณจะได้เรียนรู้วิธีควบคุมจำนวน concurrent tasks ลด resource leak และออกแบบระบบที่ “รองรับโหลดระดับหมื่นคอนเนกชันต่อวินาที” ได้จริง! 🚀