การดู : 0

12/04/2026 18:16น.

JS2GO EP.41 การจัดการ Concurrency Patterns ขั้นสูงใน Go และ JavaScript

JS2GO EP.41 การจัดการ Concurrency Patterns ขั้นสูงใน Go และ JavaScript

#Fan-in Fan-out

#Worker Pool

#JavaScript

#Go

#Concurrency

ในระบบขนาดใหญ่ Concurrency ไม่ได้แค่ “รันงานหลายอย่างพร้อมกัน” แต่คือ การออกแบบวิธีจัดลำดับ ควบคุม และส่งต่อข้อมูล ระหว่าง tasks ให้ เสถียร รวดเร็ว และประหยัดทรัพยากร ทั้ง JavaScript (Node.js) และ Go (Golang) รองรับ concurrency

 

แต่มีสถาปัตยกรรมต่างกันอย่างมาก:

ภาษาโมเดลหลัก
JavaScriptEvent Loop + async/await (single-threaded)
GoGoroutines + 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

 

สรุปเปรียบเทียบ

 

PatternGoJavaScript
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 และออกแบบระบบที่ “รองรับโหลดระดับหมื่นคอนเนกชันต่อวินาที” ได้จริง! 🚀