HTML5 Web Workers - มัลติเธรดสำหรับเว็บเบราว์เซอร์
ในบทเรียนก่อนหน้า เราได้สำรวจ HTML5 Web Storage ซึ่งเป็นวิธีที่ยอดเยี่ยมในการจัดเก็บข้อมูลในเครื่องของผู้ใช้ ในบทนี้ เราจะมาดู HTML5 Web Workers ซึ่งเป็นเทคโนโลยีที่ช่วยให้เราสามารถรัน JavaScript ใน background thread ได้อย่างอิสระ ทำให้เว็บไซต์ของเราตอบสนองได้ดีขึ้น แม้จะมีการประมวลผลที่หนักหน่วงเกิดขึ้นเบื้องหลังก็ตาม
ทำไมต้องใช้ Web Workers?
โดยปกติแล้ว JavaScript จะทำงานใน single thread ซึ่งหมายความว่าการประมวลผลที่ใช้เวลานาน เช่น การคำนวณที่ซับซ้อน การจัดการข้อมูลขนาดใหญ่ หรือการ render กราฟิก อาจทำให้ UI ของเว็บไซต์หยุดชะงักหรือ "ค้าง" ไปชั่วขณะ Web Workers ช่วยแก้ไขปัญหานี้โดยการอนุญาตให้เราย้ายการประมวลผลเหล่านั้นไปยัง background thread ซึ่งจะไม่ส่งผลกระทบต่อ responsiveness ของ UI
การสร้าง Web Worker
การสร้าง Web Worker นั้นค่อนข้างง่าย สิ่งที่เราต้องมีคือไฟล์ JavaScript แยกต่างหาก ซึ่งจะประกอบด้วยโค้ดที่เราต้องการให้รันใน background thread จากนั้น เราจะสร้าง instance ของ Web Worker ใน main thread และกำหนดไฟล์ JavaScript นั้นให้กับ instance นั้น
ตัวอย่าง:
worker.js (ไฟล์ JavaScript สำหรับ Web Worker):
// worker.js
self.addEventListener('message', function(e) {
const data = e.data;
console.log('Worker: Message received from main script');
// จำลองการคำนวณที่ใช้เวลานาน
let result = 0;
for (let i = 0; i < data; i++) {
result += i;
}
self.postMessage(result);
console.log('Worker: Posting message back to main script');
});
index.html (ไฟล์ HTML หลัก):
<!DOCTYPE html>
<html>
<head>
<title>Web Workers Example</title>
</head>
<body>
<h1>Web Workers Example</h1>
<button id="startButton">Start Calculation</button>
<p>Result: <span id="result"></span></p>
<script>
const startButton = document.getElementById('startButton');
const resultElement = document.getElementById('result');
if (window.Worker) {
const myWorker = new Worker('worker.js');
startButton.onclick = function() {
const number = 100000000; // จำนวน iterations ที่เยอะเพื่อจำลองการทำงานนาน
myWorker.postMessage(number);
console.log('Main script: Message posted to worker');
}
myWorker.onmessage = function(e) {
resultElement.textContent = e.data;
console.log('Main script: Message received from worker');
}
myWorker.onerror = function(e) {
console.log('Main script: Error occurred in worker: ' + e.message);
}
} else {
console.log('Your browser doesn\'t support web workers.');
}
</script>
</body>
</html>
คำอธิบาย:
- ในไฟล์
worker.jsเราใช้self.addEventListener('message', function(e) { ... });เพื่อรับ messages จาก main thread และself.postMessage(result);เพื่อส่ง messages กลับไปยัง main thread - ในไฟล์
index.htmlเราสร้าง instance ของ Web Worker โดยใช้new Worker('worker.js'); - เราใช้
myWorker.postMessage(number);เพื่อส่งข้อมูลไปยัง Web Worker และmyWorker.onmessage = function(e) { ... };เพื่อรับข้อมูลกลับจาก Web Worker - เราใช้
myWorker.onerror = function(e) { ... };เพื่อจัดการกับข้อผิดพลาดที่เกิดขึ้นใน Web Worker
การส่งข้อมูลระหว่าง Main Thread และ Web Worker
Web Workers สื่อสารกับ main thread โดยใช้ message passing ซึ่งหมายความว่าข้อมูลจะถูกส่งเป็น messages แทนที่จะแชร์ memory โดยตรง วิธีนี้ช่วยป้องกันปัญหา race condition และทำให้โค้ดปลอดภัยยิ่งขึ้น
เราใช้ postMessage() method เพื่อส่งข้อมูล และ onmessage event handler เพื่อรับข้อมูล ทั้ง main thread และ Web Worker สามารถใช้ methods เหล่านี้ได้
ประเภทข้อมูลที่สามารถส่งได้:
- Primitive types (string, number, boolean, null, undefined)
- Plain JavaScript objects
- Arrays
- Transferable objects (ArrayBuffer, MessagePort, ImageBitmap)
Transferable Objects:
Transferable objects คือ objects ที่สามารถ "ถ่ายโอน" ownership ไปยัง Web Worker ได้ เมื่อ object ถูกถ่ายโอนแล้ว main thread จะไม่สามารถเข้าถึง object นั้นได้อีกต่อไป วิธีนี้มีประสิทธิภาพมากกว่าการ copy ข้อมูล เนื่องจากไม่จำเป็นต้องสร้างสำเนาของข้อมูล
ข้อจำกัดของ Web Workers
แม้ว่า Web Workers จะมีประโยชน์อย่างมาก แต่ก็มีข้อจำกัดบางประการที่เราควรทราบ:
- DOM Access: Web Workers ไม่สามารถเข้าถึง DOM ได้โดยตรง ซึ่งหมายความว่าเราไม่สามารถใช้ Web Workers เพื่อแก้ไข HTML elements ได้โดยตรง เราต้องส่งข้อมูลกลับไปยัง main thread และให้ main thread ทำการแก้ไข DOM
- Window Object: Web Workers ไม่สามารถเข้าถึง
windowobject ได้ ซึ่งหมายความว่าเราไม่สามารถใช้window.alert(),window.confirm()หรือwindow.locationใน Web Workers - Global Variables: Web Workers มี global scope ของตัวเอง ซึ่งแตกต่างจาก main thread ดังนั้น global variables ที่กำหนดใน main thread จะไม่สามารถเข้าถึงได้ใน Web Worker และในทางกลับกัน
- File Access: การเข้าถึงไฟล์โดยตรงอาจถูกจำกัด ขึ้นอยู่กับเบราว์เซอร์และนโยบายความปลอดภัย
การใช้งาน Web Workers ในงานที่ใช้เวลานาน
Web Workers เหมาะอย่างยิ่งสำหรับงานที่ใช้เวลานานและอาจทำให้ UI หยุดชะงัก ตัวอย่างเช่น:
- Image Processing: การปรับขนาด, การกรอง, หรือการแปลงภาพ
- Data Analysis: การประมวลผลข้อมูลขนาดใหญ่, การสร้างกราฟ, หรือการคำนวณทางสถิติ
- Cryptographic Operations: การเข้ารหัสหรือถอดรหัสข้อมูล
- Physics Simulations: การจำลองการเคลื่อนที่ของวัตถุ
- Ray Tracing: การสร้างภาพสามมิติจากแสงและเงา
ตัวอย่าง: Image Processing
สมมติว่าเราต้องการปรับขนาดภาพขนาดใหญ่ในเว็บไซต์ของเรา หากเราทำการปรับขนาดภาพใน main thread อาจทำให้ UI หยุดชะงัก ในทางกลับกัน เราสามารถใช้ Web Worker เพื่อทำการปรับขนาดภาพใน background thread ได้
index.html (ไฟล์ HTML หลัก):
<!DOCTYPE html>
<html>
<head>
<title>Web Worker Image Processing</title>
</head>
<body>
<h1>Web Worker Image Processing</h1>
<input type="file" id="imageInput">
<canvas id="outputCanvas"></canvas>
<script>
const imageInput = document.getElementById('imageInput');
const outputCanvas = document.getElementById('outputCanvas');
const ctx = outputCanvas.getContext('2d');
if (window.Worker) {
const imageWorker = new Worker('imageWorker.js');
imageInput.addEventListener('change', function(e) {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = function(event) {
const img = new Image();
img.onload = function() {
// ส่ง ImageBitmap ไปยัง Web Worker
createImageBitmap(img).then(bitmap => {
imageWorker.postMessage({ image: bitmap, width: 200, height: 150 }, [bitmap]); // Transferable object
});
}
img.src = event.target.result;
}
reader.readAsDataURL(file);
});
imageWorker.onmessage = function(e) {
const imageData = e.data.imageData;
const width = e.data.width;
const height = e.data.height;
outputCanvas.width = width;
outputCanvas.height = height;
ctx.putImageData(imageData, 0, 0);
}
imageWorker.onerror = function(e) {
console.log('Error occurred in worker: ' + e.message);
}
} else {
console.log('Your browser doesn\'t support web workers.');
}
</script>
</body>
</html>
imageWorker.js (ไฟล์ JavaScript สำหรับ Web Worker):
self.addEventListener('message', function(e) {
const imageBitmap = e.data.image;
const width = e.data.width;
const height = e.data.height;
const offscreenCanvas = new OffscreenCanvas(width, height);
const offscreenCtx = offscreenCanvas.getContext('2d');
offscreenCtx.drawImage(imageBitmap, 0, 0, width, height);
const imageData = offscreenCtx.getImageData(0, 0, width, height);
self.postMessage({ imageData: imageData, width: width, height: height }, [imageData.data.buffer]); // Transferable object
});
คำอธิบาย:
- เราใช้
createImageBitmap()เพื่อสร้าง ImageBitmap จาก Image object และส่ง ImageBitmap ไปยัง Web Worker เป็น Transferable object - ใน Web Worker เราใช้
OffscreenCanvasเพื่อ render ImageBitmap ลงบน canvas และดึงImageDataออกมา - เราส่ง
ImageDataกลับไปยัง main thread เป็น Transferable object โดยส่งเฉพาะimageData.data.bufferเพื่อประสิทธิภาพสูงสุด - ใน main thread เราใช้
ctx.putImageData()เพื่อแสดงImageDataบน canvas
สรุป
HTML5 Web Workers เป็นเครื่องมือที่มีประสิทธิภาพสำหรับการเพิ่มประสิทธิภาพของเว็บไซต์โดยการรัน JavaScript ใน background thread ช่วยให้เราสามารถจัดการกับงานที่ใช้เวลานานโดยไม่ทำให้ UI หยุดชะงัก แม้จะมีข้อจำกัดบางประการ แต่ Web Workers ก็เป็นส่วนเสริมที่ยอดเยี่ยมสำหรับชุดเครื่องมือของนักพัฒนาเว็บ
ในบทถัดไป เราจะมาสำรวจ HTML5 Drag and Drop API ซึ่งเป็นวิธีที่ง่ายและสะดวกในการเพิ่มฟังก์ชันการลากและวางให้กับเว็บไซต์ของเรา